@@ -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:: Bolt11PaymentMetadata ;
5354use crate :: runtime:: Runtime ;
5455use crate :: types:: {
5556 CustomTlvRecord , DynStore , KeysManager , OnionMessenger , PaymentStore , Sweeper , Wallet ,
@@ -581,6 +582,35 @@ where
581582 }
582583 }
583584
585+ fn fail_claimable_payment (
586+ & self , payment_id : PaymentId , payment_hash : & PaymentHash ,
587+ ) -> Result < ( ) , ReplayEvent > {
588+ self . channel_manager . fail_htlc_backwards ( payment_hash) ;
589+
590+ let update = PaymentDetailsUpdate {
591+ status : Some ( PaymentStatus :: Failed ) ,
592+ ..PaymentDetailsUpdate :: new ( payment_id)
593+ } ;
594+ match self . payment_store . update ( update) {
595+ Ok ( _) => Ok ( ( ) ) ,
596+ Err ( e) => {
597+ log_error ! ( self . logger, "Failed to access payment store: {}" , e) ;
598+ Err ( ReplayEvent ( ) )
599+ } ,
600+ }
601+ }
602+
603+ fn lsps2_max_total_opening_fee_msat ( payment_metadata : & [ u8 ] , amount_msat : u64 ) -> Option < u64 > {
604+ let metadata = Bolt11PaymentMetadata :: read ( & mut & payment_metadata[ ..] ) . ok ( ) ?;
605+ let lsps2_parameters = metadata. lsps2_parameters ?;
606+ lsps2_parameters. max_total_opening_fee_msat . or_else ( || {
607+ lsps2_parameters. max_proportional_opening_fee_ppm_msat . and_then ( |max_prop_fee| {
608+ // If it's a variable amount payment, compute the actual fee.
609+ compute_opening_fee ( amount_msat, 0 , max_prop_fee)
610+ } )
611+ } )
612+ }
613+
584614 pub async fn handle_event ( & self , event : LdkEvent ) -> Result < ( ) , ReplayEvent > {
585615 match event {
586616 LdkEvent :: FundingGenerationReady {
@@ -694,7 +724,8 @@ where
694724 ..
695725 } => {
696726 let payment_id = PaymentId ( payment_hash. 0 ) ;
697- if let Some ( info) = self . payment_store . get ( & payment_id) {
727+ let payment_info = self . payment_store . get ( & payment_id) ;
728+ if let Some ( info) = payment_info. as_ref ( ) {
698729 if info. direction == PaymentDirection :: Outbound {
699730 log_info ! (
700731 self . logger,
@@ -717,14 +748,13 @@ where
717748 }
718749
719750 if info. status == PaymentStatus :: Succeeded
720- || matches ! ( info. kind, PaymentKind :: Spontaneous { .. } )
751+ || matches ! ( & info. kind, PaymentKind :: Spontaneous { .. } )
721752 {
722- let stored_preimage = match info. kind {
753+ let stored_preimage = match & info. kind {
723754 PaymentKind :: Bolt11 { preimage, .. }
724- | PaymentKind :: Bolt11Jit { preimage, .. }
725755 | PaymentKind :: Bolt12Offer { preimage, .. }
726756 | PaymentKind :: Bolt12Refund { preimage, .. }
727- | PaymentKind :: Spontaneous { preimage, .. } => preimage,
757+ | PaymentKind :: Spontaneous { preimage, .. } => * preimage,
728758 _ => None ,
729759 } ;
730760
@@ -759,22 +789,28 @@ where
759789 } ,
760790 } ;
761791 }
792+ }
762793
763- let max_total_opening_fee_msat = match info. kind {
764- PaymentKind :: Bolt11Jit { lsp_fee_limits, .. } => {
765- lsp_fee_limits
766- . max_total_opening_fee_msat
767- . or_else ( || {
768- lsp_fee_limits. max_proportional_opening_fee_ppm_msat . and_then (
769- |max_prop_fee| {
770- // If it's a variable amount payment, compute the actual fee.
771- compute_opening_fee ( amount_msat, 0 , max_prop_fee)
772- } ,
773- )
774- } )
775- . unwrap_or ( 0 )
776- } ,
777- _ => 0 ,
794+ if counterparty_skimmed_fee_msat > 0 {
795+ let max_total_opening_fee_msat = match & purpose {
796+ PaymentPurpose :: Bolt11InvoicePayment { .. } => onion_fields
797+ . as_ref ( )
798+ . and_then ( |fields| fields. payment_metadata . as_ref ( ) )
799+ . and_then ( |metadata| {
800+ Self :: lsps2_max_total_opening_fee_msat ( metadata, amount_msat)
801+ } ) ,
802+ _ => None ,
803+ } ;
804+
805+ let Some ( max_total_opening_fee_msat) = max_total_opening_fee_msat else {
806+ log_info ! (
807+ self . logger,
808+ "Refusing inbound payment with hash {} as the counterparty withheld {}msat without valid BOLT11 LSPS2 payment metadata" ,
809+ hex_utils:: to_string( & payment_hash. 0 ) ,
810+ counterparty_skimmed_fee_msat,
811+ ) ;
812+ self . fail_claimable_payment ( payment_id, & payment_hash) ?;
813+ return Ok ( ( ) ) ;
778814 } ;
779815
780816 if counterparty_skimmed_fee_msat > max_total_opening_fee_msat {
@@ -785,26 +821,13 @@ where
785821 counterparty_skimmed_fee_msat,
786822 max_total_opening_fee_msat,
787823 ) ;
788- self . channel_manager . fail_htlc_backwards ( & payment_hash) ;
789-
790- let update = PaymentDetailsUpdate {
791- hash : Some ( Some ( payment_hash) ) ,
792- status : Some ( PaymentStatus :: Failed ) ,
793- ..PaymentDetailsUpdate :: new ( payment_id)
794- } ;
795- match self . payment_store . update ( update) {
796- Ok ( _) => return Ok ( ( ) ) ,
797- Err ( e) => {
798- log_error ! ( self . logger, "Failed to access payment store: {}" , e) ;
799- return Err ( ReplayEvent ( ) ) ;
800- } ,
801- } ;
824+ self . fail_claimable_payment ( payment_id, & payment_hash) ?;
825+ return Ok ( ( ) ) ;
802826 }
803827
804- // If the LSP skimmed anything, update our stored payment.
805- if counterparty_skimmed_fee_msat > 0 {
806- match info. kind {
807- PaymentKind :: Bolt11Jit { .. } => {
828+ if let Some ( info) = payment_info. as_ref ( ) {
829+ match & info. kind {
830+ PaymentKind :: Bolt11 { .. } => {
808831 let update = PaymentDetailsUpdate {
809832 counterparty_skimmed_fee_msat : Some ( Some ( counterparty_skimmed_fee_msat) ) ,
810833 ..PaymentDetailsUpdate :: new ( payment_id)
@@ -817,16 +840,17 @@ where
817840 } ,
818841 } ;
819842 }
820- _ => debug_assert ! ( false , "We only expect the counterparty to get away with withholding fees for JIT payments." ) ,
843+ _ => debug_assert ! ( false , "We only expect the counterparty to get away with withholding fees for BOLT11 payments." ) ,
821844 }
822845 }
846+ }
823847
848+ if let Some ( info) = payment_info {
824849 // If this is known by the store but ChannelManager doesn't know the preimage,
825850 // the payment has been registered via `_for_hash` variants and needs to be manually claimed via
826851 // user interaction.
827852 match info. kind {
828- PaymentKind :: Bolt11 { preimage, .. }
829- | PaymentKind :: Bolt11Jit { preimage, .. } => {
853+ PaymentKind :: Bolt11 { preimage, .. } => {
830854 if purpose. preimage ( ) . is_none ( ) {
831855 debug_assert ! (
832856 preimage. is_none( ) ,
@@ -1897,8 +1921,44 @@ mod tests {
18971921
18981922 use super :: * ;
18991923 use crate :: io:: test_utils:: InMemoryStore ;
1924+ use crate :: payment:: store:: LSPS2Parameters ;
19001925 use crate :: types:: DynStoreWrapper ;
19011926
1927+ #[ test]
1928+ fn lsps2_payment_metadata_decodes_total_fee_limit ( ) {
1929+ let metadata = Bolt11PaymentMetadata {
1930+ lsps2_parameters : Some ( LSPS2Parameters {
1931+ max_total_opening_fee_msat : Some ( 42_000 ) ,
1932+ max_proportional_opening_fee_ppm_msat : None ,
1933+ } ) ,
1934+ } ;
1935+
1936+ assert_eq ! (
1937+ EventHandler :: <Arc <TestLogger >>:: lsps2_max_total_opening_fee_msat(
1938+ & metadata. encode( ) ,
1939+ 100_000
1940+ ) ,
1941+ Some ( 42_000 )
1942+ ) ;
1943+ }
1944+
1945+ #[ test]
1946+ fn lsps2_payment_metadata_missing_or_malformed_limit_is_rejected ( ) {
1947+ let empty_metadata = Bolt11PaymentMetadata { lsps2_parameters : None } . 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+ }
1961+
19021962 #[ tokio:: test]
19031963 async fn event_queue_persistence ( ) {
19041964 let store: Arc < DynStore > = Arc :: new ( DynStoreWrapper ( InMemoryStore :: new ( ) ) ) ;
0 commit comments