@@ -328,6 +328,36 @@ pub struct TrampolineForwardTlvs {
328328 pub next_blinding_override : Option < PublicKey > ,
329329}
330330
331+ /// TLVs carried by a dummy hop within a blinded payment path.
332+ ///
333+ /// Dummy hops do not correspond to real forwarding decisions, but are processed
334+ /// identically to real hops at the protocol level. The TLVs contained here define
335+ /// the relay requirements and constraints that must be satisfied for the payment
336+ /// to continue through this hop.
337+ ///
338+ /// By enforcing realistic relay semantics on dummy hops, the payment path remains
339+ /// indistinguishable from a fully real route with respect to fees, CLTV deltas, and
340+ /// validation behavior.
341+ pub struct DummyTlvs {
342+ /// Relay requirements (fees and CLTV delta) that must be satisfied when
343+ /// processing this dummy hop.
344+ pub payment_relay : PaymentRelay ,
345+ /// Constraints that apply to the payment when relaying over this dummy hop.
346+ pub payment_constraints : PaymentConstraints ,
347+ }
348+
349+ impl Default for DummyTlvs {
350+ fn default ( ) -> Self {
351+ let payment_relay =
352+ PaymentRelay { cltv_expiry_delta : 0 , fee_proportional_millionths : 0 , fee_base_msat : 0 } ;
353+
354+ let payment_constraints =
355+ PaymentConstraints { max_cltv_expiry : u32:: MAX , htlc_minimum_msat : 0 } ;
356+
357+ Self { payment_relay, payment_constraints }
358+ }
359+ }
360+
331361/// Data to construct a [`BlindedHop`] for receiving a payment. This payload is custom to LDK and
332362/// may not be valid if received by another lightning implementation.
333363#[ derive( Clone , Debug ) ]
@@ -346,6 +376,8 @@ pub struct ReceiveTlvs {
346376pub ( crate ) enum BlindedPaymentTlvs {
347377 /// This blinded payment data is for a forwarding node.
348378 Forward ( ForwardTlvs ) ,
379+ /// This blinded payment data is dummy and is to be peeled by receiving node.
380+ Dummy ( DummyTlvs ) ,
349381 /// This blinded payment data is for the receiving node.
350382 Receive ( ReceiveTlvs ) ,
351383}
@@ -363,6 +395,7 @@ pub(crate) enum BlindedTrampolineTlvs {
363395// Used to include forward and receive TLVs in the same iterator for encoding.
364396enum BlindedPaymentTlvsRef < ' a > {
365397 Forward ( & ' a ForwardTlvs ) ,
398+ Dummy ( & ' a DummyTlvs ) ,
366399 Receive ( & ' a ReceiveTlvs ) ,
367400}
368401
@@ -512,6 +545,17 @@ impl Writeable for TrampolineForwardTlvs {
512545 }
513546}
514547
548+ impl Writeable for DummyTlvs {
549+ fn write < W : Writer > ( & self , w : & mut W ) -> Result < ( ) , io:: Error > {
550+ encode_tlv_stream ! ( w, {
551+ ( 10 , self . payment_relay, required) ,
552+ ( 12 , self . payment_constraints, required) ,
553+ ( 65539 , ( ) , required) ,
554+ } ) ;
555+ Ok ( ( ) )
556+ }
557+ }
558+
515559// Note: The `authentication` TLV field was removed in LDK v0.3 following
516560// the introduction of `ReceiveAuthKey`-based authentication for inbound
517561// `BlindedPaymentPaths`s. Because we do not support receiving to those
@@ -532,6 +576,7 @@ impl<'a> Writeable for BlindedPaymentTlvsRef<'a> {
532576 fn write < W : Writer > ( & self , w : & mut W ) -> Result < ( ) , io:: Error > {
533577 match self {
534578 Self :: Forward ( tlvs) => tlvs. write ( w) ?,
579+ Self :: Dummy ( tlvs) => tlvs. write ( w) ?,
535580 Self :: Receive ( tlvs) => tlvs. write ( w) ?,
536581 }
537582 Ok ( ( ) )
@@ -552,28 +597,41 @@ impl Readable for BlindedPaymentTlvs {
552597 ( 14 , features, ( option, encoding: ( BlindedHopFeatures , WithoutLength ) ) ) ,
553598 ( 65536 , payment_secret, option) ,
554599 ( 65537 , payment_context, option) ,
600+ ( 65539 , is_dummy, option)
555601 } ) ;
556602
557- if let Some ( short_channel_id) = scid {
558- if payment_secret. is_some ( ) {
559- return Err ( DecodeError :: InvalidValue ) ;
560- }
561- Ok ( BlindedPaymentTlvs :: Forward ( ForwardTlvs {
562- short_channel_id,
563- payment_relay : payment_relay. ok_or ( DecodeError :: InvalidValue ) ?,
564- payment_constraints : payment_constraints. 0 . unwrap ( ) ,
565- next_blinding_override,
566- features : features. unwrap_or_else ( BlindedHopFeatures :: empty) ,
567- } ) )
568- } else {
569- if payment_relay. is_some ( ) || features. is_some ( ) {
570- return Err ( DecodeError :: InvalidValue ) ;
571- }
572- Ok ( BlindedPaymentTlvs :: Receive ( ReceiveTlvs {
573- payment_secret : payment_secret. ok_or ( DecodeError :: InvalidValue ) ?,
574- payment_constraints : payment_constraints. 0 . unwrap ( ) ,
575- payment_context : payment_context. ok_or ( DecodeError :: InvalidValue ) ?,
576- } ) )
603+ match (
604+ scid,
605+ next_blinding_override,
606+ payment_relay,
607+ features,
608+ payment_secret,
609+ payment_context,
610+ is_dummy,
611+ ) {
612+ ( Some ( short_channel_id) , next_override, Some ( relay) , features, None , None , None ) => {
613+ Ok ( BlindedPaymentTlvs :: Forward ( ForwardTlvs {
614+ short_channel_id,
615+ payment_relay : relay,
616+ payment_constraints : payment_constraints. 0 . unwrap ( ) ,
617+ next_blinding_override : next_override,
618+ features : features. unwrap_or_else ( BlindedHopFeatures :: empty) ,
619+ } ) )
620+ } ,
621+ ( None , None , None , None , Some ( secret) , Some ( context) , None ) => {
622+ Ok ( BlindedPaymentTlvs :: Receive ( ReceiveTlvs {
623+ payment_secret : secret,
624+ payment_constraints : payment_constraints. 0 . unwrap ( ) ,
625+ payment_context : context,
626+ } ) )
627+ } ,
628+ ( None , None , Some ( relay) , None , None , None , Some ( ( ) ) ) => {
629+ Ok ( BlindedPaymentTlvs :: Dummy ( DummyTlvs {
630+ payment_relay : relay,
631+ payment_constraints : payment_constraints. 0 . unwrap ( ) ,
632+ } ) )
633+ } ,
634+ _ => return Err ( DecodeError :: InvalidValue ) ,
577635 }
578636 }
579637}
0 commit comments