77
88use core:: future:: Future ;
99use core:: task:: { Poll , Waker } ;
10- use std:: collections:: VecDeque ;
10+ use std:: collections:: { BTreeMap , VecDeque } ;
1111use std:: ops:: Deref ;
1212use std:: sync:: { Arc , Mutex } ;
1313
@@ -664,13 +664,40 @@ where
664664 }
665665 }
666666
667- fn lsps2_max_total_opening_fee_msat ( payment_metadata : & [ u8 ] , amount_msat : u64 ) -> Option < u64 > {
667+ fn lsps2_max_total_opening_fee_msat (
668+ payment_metadata : & [ u8 ] , amount_msat : u64 , counterparty_skimmed_fee_msat : u64 ,
669+ ) -> Option < u64 > {
668670 let metadata = PaymentMetadata :: read ( & mut & payment_metadata[ ..] ) . ok ( ) ?;
671+ Self :: lsps2_max_total_opening_fee_msat_from_metadata (
672+ metadata,
673+ amount_msat,
674+ counterparty_skimmed_fee_msat,
675+ )
676+ }
677+
678+ fn lsps2_bolt12_max_total_opening_fee_msat (
679+ payment_metadata : & BTreeMap < u64 , Vec < u8 > > , amount_msat : u64 ,
680+ counterparty_skimmed_fee_msat : u64 ,
681+ ) -> Option < u64 > {
682+ let metadata = PaymentMetadata :: decode_from_bolt12_payment_metadata ( payment_metadata) ?;
683+ Self :: lsps2_max_total_opening_fee_msat_from_metadata (
684+ metadata,
685+ amount_msat,
686+ counterparty_skimmed_fee_msat,
687+ )
688+ }
689+
690+ fn lsps2_max_total_opening_fee_msat_from_metadata (
691+ metadata : PaymentMetadata , amount_msat : u64 , counterparty_skimmed_fee_msat : u64 ,
692+ ) -> Option < u64 > {
669693 let lsps2_parameters = metadata. lsps2_parameters ?;
670694 lsps2_parameters. max_total_opening_fee_msat . or_else ( || {
671695 lsps2_parameters. max_proportional_opening_fee_ppm_msat . and_then ( |max_prop_fee| {
672- // If it's a variable amount payment, compute the actual fee.
673- compute_opening_fee ( amount_msat, 0 , max_prop_fee)
696+ // `PaymentClaimable::amount_msat` excludes the fee withheld by the LSP. LSPS2
697+ // proportional fees are calculated from the amount sent by the payer, so add the
698+ // withheld fee back before computing the permitted fee.
699+ let payment_size_msat = amount_msat. checked_add ( counterparty_skimmed_fee_msat) ?;
700+ compute_opening_fee ( payment_size_msat, 0 , max_prop_fee)
674701 } )
675702 } )
676703 }
@@ -864,15 +891,28 @@ where
864891 . as_ref ( )
865892 . and_then ( |fields| fields. payment_metadata . as_ref ( ) )
866893 . and_then ( |metadata| {
867- Self :: lsps2_max_total_opening_fee_msat ( metadata, amount_msat)
894+ Self :: lsps2_max_total_opening_fee_msat (
895+ metadata,
896+ amount_msat,
897+ counterparty_skimmed_fee_msat,
898+ )
868899 } ) ,
900+ PaymentPurpose :: Bolt12OfferPayment { payment_context, .. } => {
901+ payment_context. payment_metadata . as_ref ( ) . and_then ( |metadata| {
902+ Self :: lsps2_bolt12_max_total_opening_fee_msat (
903+ metadata,
904+ amount_msat,
905+ counterparty_skimmed_fee_msat,
906+ )
907+ } )
908+ } ,
869909 _ => None ,
870910 } ;
871911
872912 let Some ( max_total_opening_fee_msat) = max_total_opening_fee_msat else {
873913 log_info ! (
874914 self . logger,
875- "Refusing inbound payment with hash {} as the counterparty withheld {}msat without valid BOLT11 LSPS2 payment metadata" ,
915+ "Refusing inbound payment with hash {} as the counterparty withheld {}msat without valid LSPS2 payment metadata" ,
876916 hex_utils:: to_string( & payment_hash. 0 ) ,
877917 counterparty_skimmed_fee_msat,
878918 ) ;
@@ -894,7 +934,7 @@ where
894934
895935 if let Some ( info) = payment_info. as_ref ( ) {
896936 match & info. kind {
897- PaymentKind :: Bolt11 { .. } => {
937+ PaymentKind :: Bolt11 { .. } | PaymentKind :: Bolt12Offer { .. } => {
898938 let update = PaymentDetailsUpdate {
899939 counterparty_skimmed_fee_msat : Some ( Some ( counterparty_skimmed_fee_msat) ) ,
900940 ..PaymentDetailsUpdate :: new ( payment_id)
@@ -907,7 +947,7 @@ where
907947 } ,
908948 } ;
909949 } ,
910- _ => debug_assert ! ( false , "We only expect the counterparty to get away with withholding fees for BOLT11 payments." ) ,
950+ _ => debug_assert ! ( false , "We only expect the counterparty to withhold fees for LSPS2 invoice payments." ) ,
911951 }
912952 }
913953 }
@@ -976,6 +1016,8 @@ where
9761016 hash : Some ( payment_hash) ,
9771017 preimage : payment_preimage,
9781018 secret : Some ( payment_secret) ,
1019+ counterparty_skimmed_fee_msat : ( counterparty_skimmed_fee_msat > 0 )
1020+ . then_some ( counterparty_skimmed_fee_msat) ,
9791021 offer_id,
9801022 payer_note,
9811023 quantity,
@@ -2038,12 +2080,33 @@ mod tests {
20382080 assert_eq ! (
20392081 EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
20402082 & metadata. encode( ) ,
2041- 100_000
2083+ 100_000 ,
2084+ 0 ,
20422085 ) ,
20432086 Some ( 42_000 )
20442087 ) ;
20452088 }
20462089
2090+ #[ test]
2091+ fn lsps2_proportional_fee_uses_gross_payment_amount ( ) {
2092+ let metadata = PaymentMetadata {
2093+ lsps2_parameters : Some ( LSPS2Parameters {
2094+ max_total_opening_fee_msat : None ,
2095+ max_proportional_opening_fee_ppm_msat : Some ( 10_000 ) ,
2096+ } ) ,
2097+ lsps2_lease_parameters : None ,
2098+ } ;
2099+
2100+ assert_eq ! (
2101+ EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
2102+ & metadata. encode( ) ,
2103+ 198_000 ,
2104+ 2_000 ,
2105+ ) ,
2106+ Some ( 2_000 )
2107+ ) ;
2108+ }
2109+
20472110 #[ test]
20482111 fn lsps2_payment_metadata_missing_or_malformed_limit_is_rejected ( ) {
20492112 let empty_metadata =
@@ -2060,23 +2123,63 @@ mod tests {
20602123 assert_eq ! (
20612124 EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
20622125 & empty_metadata,
2063- 100_000
2126+ 100_000 ,
2127+ 0 ,
20642128 ) ,
20652129 None
20662130 ) ;
20672131 assert_eq ! (
2068- EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat( & [ 0xff ] , 100_000 ) ,
2132+ EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat( & [ 0xff ] , 100_000 , 0 , ) ,
20692133 None
20702134 ) ;
20712135 assert_eq ! (
20722136 EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
20732137 & metadata_without_fee_limit,
2074- 100_000
2138+ 100_000 ,
2139+ 0 ,
20752140 ) ,
20762141 None
20772142 ) ;
20782143 }
20792144
2145+ #[ test]
2146+ fn bolt12_lsps2_payment_metadata_decodes_fee_limit ( ) {
2147+ let metadata = PaymentMetadata {
2148+ lsps2_parameters : Some ( LSPS2Parameters {
2149+ max_total_opening_fee_msat : None ,
2150+ max_proportional_opening_fee_ppm_msat : Some ( 10_000 ) ,
2151+ } ) ,
2152+ lsps2_lease_parameters : None ,
2153+ }
2154+ . encode_as_bolt12_payment_metadata ( ) ;
2155+
2156+ assert_eq ! (
2157+ EventHandler :: <Arc <TestLogger >>:: lsps2_bolt12_max_total_opening_fee_msat(
2158+ & metadata, 100_000 , 0 ,
2159+ ) ,
2160+ Some ( 1_000 )
2161+ ) ;
2162+ }
2163+
2164+ #[ test]
2165+ fn bolt12_lsps2_payment_metadata_requires_fee_limit ( ) {
2166+ let empty_metadata = BTreeMap :: new ( ) ;
2167+ let metadata_without_fee_limit =
2168+ PaymentMetadata { lsps2_parameters : None , lsps2_lease_parameters : None }
2169+ . encode_as_bolt12_payment_metadata ( ) ;
2170+ let mut malformed_metadata = metadata_without_fee_limit. clone ( ) ;
2171+ * malformed_metadata. values_mut ( ) . next ( ) . unwrap ( ) = vec ! [ 0xff ] ;
2172+
2173+ for metadata in [ empty_metadata, malformed_metadata, metadata_without_fee_limit] {
2174+ assert_eq ! (
2175+ EventHandler :: <Arc <TestLogger >>:: lsps2_bolt12_max_total_opening_fee_msat(
2176+ & metadata, 100_000 , 0 ,
2177+ ) ,
2178+ None
2179+ ) ;
2180+ }
2181+ }
2182+
20802183 #[ tokio:: test]
20812184 async fn event_queue_persistence ( ) {
20822185 let store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
0 commit comments