@@ -50,6 +50,7 @@ use crate::payment::asynchronous::static_invoice_store::StaticInvoiceStore;
5050use crate :: payment:: store:: {
5151 PaymentDetails , PaymentDetailsUpdate , PaymentDirection , PaymentKind , PaymentStatus ,
5252} ;
53+ use crate :: payment:: PaymentMetadata ;
5354use crate :: runtime:: Runtime ;
5455use crate :: types:: {
5556 CustomTlvRecord , DynStore , KeysManager , OnionMessenger , PaymentStore , Sweeper , Wallet ,
@@ -580,6 +581,35 @@ where
580581 }
581582 }
582583
584+ fn fail_claimable_payment (
585+ & self , payment_id : PaymentId , payment_hash : & PaymentHash ,
586+ ) -> Result < ( ) , ReplayEvent > {
587+ self . channel_manager . fail_htlc_backwards ( payment_hash) ;
588+
589+ let update = PaymentDetailsUpdate {
590+ status : Some ( PaymentStatus :: Failed ) ,
591+ ..PaymentDetailsUpdate :: new ( payment_id)
592+ } ;
593+ match self . payment_store . update ( update) {
594+ Ok ( _) => Ok ( ( ) ) ,
595+ Err ( e) => {
596+ log_error ! ( self . logger, "Failed to access payment store: {}" , e) ;
597+ Err ( ReplayEvent ( ) )
598+ } ,
599+ }
600+ }
601+
602+ fn lsps2_max_total_opening_fee_msat ( payment_metadata : & [ u8 ] , amount_msat : u64 ) -> Option < u64 > {
603+ let metadata = PaymentMetadata :: read ( & mut & payment_metadata[ ..] ) . ok ( ) ?;
604+ let lsps2_parameters = metadata. lsps2_parameters ?;
605+ lsps2_parameters. max_total_opening_fee_msat . or_else ( || {
606+ lsps2_parameters. max_proportional_opening_fee_ppm_msat . and_then ( |max_prop_fee| {
607+ // If it's a variable amount payment, compute the actual fee.
608+ compute_opening_fee ( amount_msat, 0 , max_prop_fee)
609+ } )
610+ } )
611+ }
612+
583613 pub async fn handle_event ( & self , event : LdkEvent ) -> Result < ( ) , ReplayEvent > {
584614 match event {
585615 LdkEvent :: FundingGenerationReady {
@@ -693,7 +723,8 @@ where
693723 ..
694724 } => {
695725 let payment_id = PaymentId ( payment_hash. 0 ) ;
696- if let Some ( info) = self . payment_store . get ( & payment_id) {
726+ let payment_info = self . payment_store . get ( & payment_id) ;
727+ if let Some ( info) = payment_info. as_ref ( ) {
697728 if info. direction == PaymentDirection :: Outbound {
698729 log_info ! (
699730 self . logger,
@@ -716,14 +747,13 @@ where
716747 }
717748
718749 if info. status == PaymentStatus :: Succeeded
719- || matches ! ( info. kind, PaymentKind :: Spontaneous { .. } )
750+ || matches ! ( & info. kind, PaymentKind :: Spontaneous { .. } )
720751 {
721- let stored_preimage = match info. kind {
752+ let stored_preimage = match & info. kind {
722753 PaymentKind :: Bolt11 { preimage, .. }
723- | PaymentKind :: Bolt11Jit { preimage, .. }
724754 | PaymentKind :: Bolt12Offer { preimage, .. }
725755 | PaymentKind :: Bolt12Refund { preimage, .. }
726- | PaymentKind :: Spontaneous { preimage, .. } => preimage,
756+ | PaymentKind :: Spontaneous { preimage, .. } => * preimage,
727757 _ => None ,
728758 } ;
729759
@@ -758,22 +788,28 @@ where
758788 } ,
759789 } ;
760790 }
791+ }
761792
762- let max_total_opening_fee_msat = match info. kind {
763- PaymentKind :: Bolt11Jit { lsp_fee_limits, .. } => {
764- lsp_fee_limits
765- . max_total_opening_fee_msat
766- . or_else ( || {
767- lsp_fee_limits. max_proportional_opening_fee_ppm_msat . and_then (
768- |max_prop_fee| {
769- // If it's a variable amount payment, compute the actual fee.
770- compute_opening_fee ( amount_msat, 0 , max_prop_fee)
771- } ,
772- )
773- } )
774- . unwrap_or ( 0 )
775- } ,
776- _ => 0 ,
793+ if counterparty_skimmed_fee_msat > 0 {
794+ let max_total_opening_fee_msat = match & purpose {
795+ PaymentPurpose :: Bolt11InvoicePayment { .. } => onion_fields
796+ . as_ref ( )
797+ . and_then ( |fields| fields. payment_metadata . as_ref ( ) )
798+ . and_then ( |metadata| {
799+ Self :: lsps2_max_total_opening_fee_msat ( metadata, amount_msat)
800+ } ) ,
801+ _ => None ,
802+ } ;
803+
804+ let Some ( max_total_opening_fee_msat) = max_total_opening_fee_msat else {
805+ log_info ! (
806+ self . logger,
807+ "Refusing inbound payment with hash {} as the counterparty withheld {}msat without valid BOLT11 LSPS2 payment metadata" ,
808+ hex_utils:: to_string( & payment_hash. 0 ) ,
809+ counterparty_skimmed_fee_msat,
810+ ) ;
811+ self . fail_claimable_payment ( payment_id, & payment_hash) ?;
812+ return Ok ( ( ) ) ;
777813 } ;
778814
779815 if counterparty_skimmed_fee_msat > max_total_opening_fee_msat {
@@ -784,26 +820,13 @@ where
784820 counterparty_skimmed_fee_msat,
785821 max_total_opening_fee_msat,
786822 ) ;
787- self . channel_manager . fail_htlc_backwards ( & payment_hash) ;
788-
789- let update = PaymentDetailsUpdate {
790- hash : Some ( Some ( payment_hash) ) ,
791- status : Some ( PaymentStatus :: Failed ) ,
792- ..PaymentDetailsUpdate :: new ( payment_id)
793- } ;
794- match self . payment_store . update ( update) {
795- Ok ( _) => return Ok ( ( ) ) ,
796- Err ( e) => {
797- log_error ! ( self . logger, "Failed to access payment store: {}" , e) ;
798- return Err ( ReplayEvent ( ) ) ;
799- } ,
800- } ;
823+ self . fail_claimable_payment ( payment_id, & payment_hash) ?;
824+ return Ok ( ( ) ) ;
801825 }
802826
803- // If the LSP skimmed anything, update our stored payment.
804- if counterparty_skimmed_fee_msat > 0 {
805- match info. kind {
806- PaymentKind :: Bolt11Jit { .. } => {
827+ if let Some ( info) = payment_info. as_ref ( ) {
828+ match & info. kind {
829+ PaymentKind :: Bolt11 { .. } => {
807830 let update = PaymentDetailsUpdate {
808831 counterparty_skimmed_fee_msat : Some ( Some ( counterparty_skimmed_fee_msat) ) ,
809832 ..PaymentDetailsUpdate :: new ( payment_id)
@@ -816,16 +839,17 @@ where
816839 } ,
817840 } ;
818841 }
819- _ => debug_assert ! ( false , "We only expect the counterparty to get away with withholding fees for JIT payments." ) ,
842+ _ => debug_assert ! ( false , "We only expect the counterparty to get away with withholding fees for BOLT11 payments." ) ,
820843 }
821844 }
845+ }
822846
847+ if let Some ( info) = payment_info {
823848 // If this is known by the store but ChannelManager doesn't know the preimage,
824849 // the payment has been registered via `_for_hash` variants and needs to be manually claimed via
825850 // user interaction.
826851 match info. kind {
827- PaymentKind :: Bolt11 { preimage, .. }
828- | PaymentKind :: Bolt11Jit { preimage, .. } => {
852+ PaymentKind :: Bolt11 { preimage, .. } => {
829853 if purpose. preimage ( ) . is_none ( ) {
830854 debug_assert ! (
831855 preimage. is_none( ) ,
@@ -1890,8 +1914,58 @@ mod tests {
18901914
18911915 use super :: * ;
18921916 use crate :: io:: test_utils:: InMemoryStore ;
1917+ use crate :: payment:: store:: LSPS2Parameters ;
18931918 use crate :: types:: DynStoreWrapper ;
18941919
1920+ #[ test]
1921+ fn lsps2_payment_metadata_decodes_total_fee_limit ( ) {
1922+ let metadata = PaymentMetadata {
1923+ lsps2_parameters : Some ( LSPS2Parameters {
1924+ max_total_opening_fee_msat : Some ( 42_000 ) ,
1925+ max_proportional_opening_fee_ppm_msat : None ,
1926+ } ) ,
1927+ } ;
1928+
1929+ assert_eq ! (
1930+ EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
1931+ & metadata. encode( ) ,
1932+ 100_000
1933+ ) ,
1934+ Some ( 42_000 )
1935+ ) ;
1936+ }
1937+
1938+ #[ test]
1939+ fn lsps2_payment_metadata_missing_or_malformed_limit_is_rejected ( ) {
1940+ let empty_metadata = PaymentMetadata { lsps2_parameters : None } . encode ( ) ;
1941+ let metadata_without_fee_limit = PaymentMetadata {
1942+ lsps2_parameters : Some ( LSPS2Parameters {
1943+ max_total_opening_fee_msat : None ,
1944+ max_proportional_opening_fee_ppm_msat : None ,
1945+ } ) ,
1946+ }
1947+ . encode ( ) ;
1948+
1949+ assert_eq ! (
1950+ EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
1951+ & empty_metadata,
1952+ 100_000
1953+ ) ,
1954+ None
1955+ ) ;
1956+ assert_eq ! (
1957+ EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat( & [ 0xff ] , 100_000 ) ,
1958+ None
1959+ ) ;
1960+ assert_eq ! (
1961+ EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
1962+ & metadata_without_fee_limit,
1963+ 100_000
1964+ ) ,
1965+ None
1966+ ) ;
1967+ }
1968+
18951969 #[ tokio:: test]
18961970 async fn event_queue_persistence ( ) {
18971971 let store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
0 commit comments