@@ -749,6 +749,11 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
749749 /// [fake scids]: crate::util::scid_utils::fake_scid
750750 fake_scid_rand_bytes : [ u8 ; 32 ] ,
751751
752+ /// When we send payment probes, we generate the [`PaymentHash`] based on this cookie secret
753+ /// and a random [`PaymentId`]. This allows us to discern probes from real payments, without
754+ /// keeping additional state.
755+ probing_cookie_secret : [ u8 ; 32 ] ,
756+
752757 /// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
753758 /// value increases strictly since we don't assume access to a time source.
754759 last_node_announcement_serial : AtomicUsize ,
@@ -1605,6 +1610,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
16051610 inbound_payment_key : expanded_inbound_key,
16061611 fake_scid_rand_bytes : keys_manager. get_secure_random_bytes ( ) ,
16071612
1613+ probing_cookie_secret : keys_manager. get_secure_random_bytes ( ) ,
1614+
16081615 last_node_announcement_serial : AtomicUsize :: new ( 0 ) ,
16091616 highest_seen_timestamp : AtomicUsize :: new ( 0 ) ,
16101617
@@ -2722,6 +2729,49 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
27222729 }
27232730 }
27242731
2732+ /// Send a payment that is probing the given route for liquidity. We calculate the
2733+ /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
2734+ /// us to easily discern them from real payments. This can be checked by calling
2735+ /// [`payment_is_probe`].
2736+ ///
2737+ /// [`payment_is_probe`]: Self::payment_is_probe
2738+ pub fn send_probe_payment ( & self , route : & Route ) -> Result < ( PaymentHash , PaymentId ) , PaymentSendFailure > {
2739+ let payment_id = PaymentId ( self . keys_manager . get_secure_random_bytes ( ) ) ;
2740+
2741+ let mut preimage = [ 0u8 ; 32 ] ;
2742+ preimage[ ..16 ] . copy_from_slice ( & self . probing_cookie_secret [ ..16 ] ) ;
2743+ preimage[ 16 ..] . copy_from_slice ( & payment_id. 0 [ ..16 ] ) ;
2744+
2745+ let payment_hash = PaymentHash ( Sha256 :: hash ( & preimage) . into_inner ( ) ) ;
2746+
2747+ if let Some ( params) = & route. payment_params {
2748+ if params. max_path_count != 1 {
2749+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
2750+ err : "Probe payments need to be sent over a single path" . to_string ( )
2751+ } ) )
2752+ }
2753+ } else {
2754+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
2755+ err : "No parameters provided" . to_string ( )
2756+ } ) )
2757+ }
2758+
2759+ match self . send_payment_internal ( route, payment_hash, & None , None , Some ( payment_id) , None ) {
2760+ Ok ( payment_id) => Ok ( ( payment_hash, payment_id) ) ,
2761+ Err ( e) => Err ( e)
2762+ }
2763+ }
2764+
2765+ /// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
2766+ /// payment probe.
2767+ pub fn payment_is_probe ( & self , payment_hash : PaymentHash , payment_id : PaymentId ) -> bool {
2768+ let mut preimage = [ 0u8 ; 32 ] ;
2769+ preimage[ ..16 ] . copy_from_slice ( & self . probing_cookie_secret [ ..16 ] ) ;
2770+ preimage[ 16 ..] . copy_from_slice ( & payment_id. 0 [ ..16 ] ) ;
2771+ let target_payment_hash = PaymentHash ( Sha256 :: hash ( & preimage) . into_inner ( ) ) ;
2772+ target_payment_hash == payment_hash
2773+ }
2774+
27252775 /// Handles the generation of a funding transaction, optionally (for tests) with a function
27262776 /// which checks the correctness of the funding transaction given the associated channel.
27272777 fn funding_transaction_generated_intern < FundingOutput : Fn ( & Channel < Signer > , & Transaction ) -> Result < OutPoint , APIError > > (
@@ -6623,6 +6673,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
66236673 ( 5 , self . our_network_pubkey, required) ,
66246674 ( 7 , self . fake_scid_rand_bytes, required) ,
66256675 ( 9 , htlc_purposes, vec_type) ,
6676+ ( 11 , self . probing_cookie_secret, required) ,
66266677 } ) ;
66276678
66286679 Ok ( ( ) )
@@ -6919,18 +6970,24 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
69196970 let mut pending_outbound_payments = None ;
69206971 let mut received_network_pubkey: Option < PublicKey > = None ;
69216972 let mut fake_scid_rand_bytes: Option < [ u8 ; 32 ] > = None ;
6973+ let mut probing_cookie_secret: Option < [ u8 ; 32 ] > = None ;
69226974 let mut claimable_htlc_purposes = None ;
69236975 read_tlv_fields ! ( reader, {
69246976 ( 1 , pending_outbound_payments_no_retry, option) ,
69256977 ( 3 , pending_outbound_payments, option) ,
69266978 ( 5 , received_network_pubkey, option) ,
69276979 ( 7 , fake_scid_rand_bytes, option) ,
69286980 ( 9 , claimable_htlc_purposes, vec_type) ,
6981+ ( 11 , probing_cookie_secret, option) ,
69296982 } ) ;
69306983 if fake_scid_rand_bytes. is_none ( ) {
69316984 fake_scid_rand_bytes = Some ( args. keys_manager . get_secure_random_bytes ( ) ) ;
69326985 }
69336986
6987+ if probing_cookie_secret. is_none ( ) {
6988+ probing_cookie_secret = Some ( args. keys_manager . get_secure_random_bytes ( ) ) ;
6989+ }
6990+
69346991 if pending_outbound_payments. is_none ( ) && pending_outbound_payments_no_retry. is_none ( ) {
69356992 pending_outbound_payments = Some ( pending_outbound_payments_compat) ;
69366993 } else if pending_outbound_payments. is_none ( ) {
@@ -7136,6 +7193,8 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
71367193 outbound_scid_aliases : Mutex :: new ( outbound_scid_aliases) ,
71377194 fake_scid_rand_bytes : fake_scid_rand_bytes. unwrap ( ) ,
71387195
7196+ probing_cookie_secret : probing_cookie_secret. unwrap ( ) ,
7197+
71397198 our_network_key,
71407199 our_network_pubkey,
71417200 secp_ctx,
0 commit comments