@@ -740,6 +740,11 @@ pub struct ChannelManager<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref,
740
740
/// [fake scids]: crate::util::scid_utils::fake_scid
741
741
fake_scid_rand_bytes : [ u8 ; 32 ] ,
742
742
743
+ /// When we send payment probes, we generate the [`PaymentHash`] based on this cookie secret
744
+ /// and a random [`PaymentId`]. This allows us to discern probes from real payments, without
745
+ /// keeping additional state.
746
+ probing_cookie_secret : [ u8 ; 32 ] ,
747
+
743
748
/// Used to track the last value sent in a node_announcement "timestamp" field. We ensure this
744
749
/// value increases strictly since we don't assume access to a time source.
745
750
last_node_announcement_serial : AtomicUsize ,
@@ -1589,6 +1594,8 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
1589
1594
inbound_payment_key : expanded_inbound_key,
1590
1595
fake_scid_rand_bytes : keys_manager. get_secure_random_bytes ( ) ,
1591
1596
1597
+ probing_cookie_secret : keys_manager. get_secure_random_bytes ( ) ,
1598
+
1592
1599
last_node_announcement_serial : AtomicUsize :: new ( 0 ) ,
1593
1600
highest_seen_timestamp : AtomicUsize :: new ( 0 ) ,
1594
1601
@@ -2731,6 +2738,46 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
2731
2738
}
2732
2739
}
2733
2740
2741
+ /// Send a payment that is probing the given route for liquidity. We calculate the
2742
+ /// [`PaymentHash`] of probes based on a static secret and a random [`PaymentId`], which allows
2743
+ /// us to easily discern them from real payments. This can be checked by calling
2744
+ /// [`payment_is_probe`].
2745
+ ///
2746
+ /// [`payment_is_probe`]: Self::payment_is_probe
2747
+ pub fn send_probe ( & self , hops : Vec < RouteHop > ) -> Result < ( PaymentHash , PaymentId ) , PaymentSendFailure > {
2748
+ let payment_id = PaymentId ( self . keys_manager . get_secure_random_bytes ( ) ) ;
2749
+
2750
+ let payment_hash = self . probing_cookie_from_id ( & payment_id) ;
2751
+
2752
+ if hops. len ( ) < 2 {
2753
+ return Err ( PaymentSendFailure :: ParameterError ( APIError :: APIMisuseError {
2754
+ err : "No need probing a path with less than two hops" . to_string ( )
2755
+ } ) )
2756
+ }
2757
+
2758
+ let route = Route { paths : vec ! [ hops] , payment_params : None } ;
2759
+
2760
+ match self . send_payment_internal ( & route, payment_hash, & None , None , Some ( payment_id) , None ) {
2761
+ Ok ( payment_id) => Ok ( ( payment_hash, payment_id) ) ,
2762
+ Err ( e) => Err ( e)
2763
+ }
2764
+ }
2765
+
2766
+ /// Returns whether a payment with the given [`PaymentHash`] and [`PaymentId`] is, in fact, a
2767
+ /// payment probe.
2768
+ pub ( crate ) fn payment_is_probe ( & self , payment_hash : & PaymentHash , payment_id : & PaymentId ) -> bool {
2769
+ let target_payment_hash = self . probing_cookie_from_id ( payment_id) ;
2770
+ target_payment_hash == * payment_hash
2771
+ }
2772
+
2773
+ /// Returns the 'probing cookie' for the given [`PaymentId`].
2774
+ fn probing_cookie_from_id ( & self , payment_id : & PaymentId ) -> PaymentHash {
2775
+ let mut preimage = [ 0u8 ; 64 ] ;
2776
+ preimage[ ..32 ] . copy_from_slice ( & self . probing_cookie_secret ) ;
2777
+ preimage[ 32 ..] . copy_from_slice ( & payment_id. 0 ) ;
2778
+ PaymentHash ( Sha256 :: hash ( & preimage) . into_inner ( ) )
2779
+ }
2780
+
2734
2781
/// Handles the generation of a funding transaction, optionally (for tests) with a function
2735
2782
/// which checks the correctness of the funding transaction given the associated channel.
2736
2783
fn funding_transaction_generated_intern < FundingOutput : Fn ( & Channel < Signer > , & Transaction ) -> Result < OutPoint , APIError > > (
@@ -3839,22 +3886,40 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
3839
3886
let ( network_update, short_channel_id, payment_retryable, onion_error_code, onion_error_data) = onion_utils:: process_onion_failure ( & self . secp_ctx , & self . logger , & source, err. data . clone ( ) ) ;
3840
3887
#[ cfg( not( test) ) ]
3841
3888
let ( network_update, short_channel_id, payment_retryable, _, _) = onion_utils:: process_onion_failure ( & self . secp_ctx , & self . logger , & source, err. data . clone ( ) ) ;
3842
- // TODO: If we decided to blame ourselves (or one of our channels) in
3843
- // process_onion_failure we should close that channel as it implies our
3844
- // next-hop is needlessly blaming us!
3845
- events:: Event :: PaymentPathFailed {
3846
- payment_id : Some ( payment_id) ,
3847
- payment_hash : payment_hash. clone ( ) ,
3848
- rejected_by_dest : !payment_retryable,
3849
- network_update,
3850
- all_paths_failed,
3851
- path : path. clone ( ) ,
3852
- short_channel_id,
3853
- retry,
3854
- #[ cfg( test) ]
3855
- error_code : onion_error_code,
3856
- #[ cfg( test) ]
3857
- error_data : onion_error_data
3889
+
3890
+ if self . payment_is_probe ( payment_hash, & payment_id) {
3891
+ if !payment_retryable {
3892
+ events:: Event :: ProbeSuccessful {
3893
+ payment_id,
3894
+ payment_hash : payment_hash. clone ( ) ,
3895
+ path : path. clone ( ) ,
3896
+ }
3897
+ } else {
3898
+ events:: Event :: ProbeFailed {
3899
+ payment_id : payment_id,
3900
+ payment_hash : payment_hash. clone ( ) ,
3901
+ path : path. clone ( ) ,
3902
+ short_channel_id,
3903
+ }
3904
+ }
3905
+ } else {
3906
+ // TODO: If we decided to blame ourselves (or one of our channels) in
3907
+ // process_onion_failure we should close that channel as it implies our
3908
+ // next-hop is needlessly blaming us!
3909
+ events:: Event :: PaymentPathFailed {
3910
+ payment_id : Some ( payment_id) ,
3911
+ payment_hash : payment_hash. clone ( ) ,
3912
+ rejected_by_dest : !payment_retryable,
3913
+ network_update,
3914
+ all_paths_failed,
3915
+ path : path. clone ( ) ,
3916
+ short_channel_id,
3917
+ retry,
3918
+ #[ cfg( test) ]
3919
+ error_code : onion_error_code,
3920
+ #[ cfg( test) ]
3921
+ error_data : onion_error_data
3922
+ }
3858
3923
}
3859
3924
} ,
3860
3925
& HTLCFailReason :: Reason {
@@ -6631,6 +6696,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> Writeable f
6631
6696
( 5 , self . our_network_pubkey, required) ,
6632
6697
( 7 , self . fake_scid_rand_bytes, required) ,
6633
6698
( 9 , htlc_purposes, vec_type) ,
6699
+ ( 11 , self . probing_cookie_secret, required) ,
6634
6700
} ) ;
6635
6701
6636
6702
Ok ( ( ) )
@@ -6927,18 +6993,24 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
6927
6993
let mut pending_outbound_payments = None ;
6928
6994
let mut received_network_pubkey: Option < PublicKey > = None ;
6929
6995
let mut fake_scid_rand_bytes: Option < [ u8 ; 32 ] > = None ;
6996
+ let mut probing_cookie_secret: Option < [ u8 ; 32 ] > = None ;
6930
6997
let mut claimable_htlc_purposes = None ;
6931
6998
read_tlv_fields ! ( reader, {
6932
6999
( 1 , pending_outbound_payments_no_retry, option) ,
6933
7000
( 3 , pending_outbound_payments, option) ,
6934
7001
( 5 , received_network_pubkey, option) ,
6935
7002
( 7 , fake_scid_rand_bytes, option) ,
6936
7003
( 9 , claimable_htlc_purposes, vec_type) ,
7004
+ ( 11 , probing_cookie_secret, option) ,
6937
7005
} ) ;
6938
7006
if fake_scid_rand_bytes. is_none ( ) {
6939
7007
fake_scid_rand_bytes = Some ( args. keys_manager . get_secure_random_bytes ( ) ) ;
6940
7008
}
6941
7009
7010
+ if probing_cookie_secret. is_none ( ) {
7011
+ probing_cookie_secret = Some ( args. keys_manager . get_secure_random_bytes ( ) ) ;
7012
+ }
7013
+
6942
7014
if pending_outbound_payments. is_none ( ) && pending_outbound_payments_no_retry. is_none ( ) {
6943
7015
pending_outbound_payments = Some ( pending_outbound_payments_compat) ;
6944
7016
} else if pending_outbound_payments. is_none ( ) {
@@ -7144,6 +7216,8 @@ impl<'a, Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref>
7144
7216
outbound_scid_aliases : Mutex :: new ( outbound_scid_aliases) ,
7145
7217
fake_scid_rand_bytes : fake_scid_rand_bytes. unwrap ( ) ,
7146
7218
7219
+ probing_cookie_secret : probing_cookie_secret. unwrap ( ) ,
7220
+
7147
7221
our_network_key,
7148
7222
our_network_pubkey,
7149
7223
secp_ctx,
0 commit comments