@@ -1639,6 +1639,7 @@ mod tests {
1639
1639
NodeAnnouncement , UnsignedNodeAnnouncement , ChannelUpdate , UnsignedChannelUpdate } ;
1640
1640
use ln:: channelmanager;
1641
1641
use util:: test_utils;
1642
+ use util:: chacha20:: ChaCha20 ;
1642
1643
use util:: ser:: Writeable ;
1643
1644
#[ cfg( c_bindings) ]
1644
1645
use util:: ser:: Writer ;
@@ -5071,6 +5072,74 @@ mod tests {
5071
5072
assert_eq ! ( cltv_expiry_deltas_before, cltv_expiry_deltas_limited) ;
5072
5073
}
5073
5074
5075
+ #[ test]
5076
+ fn adds_plausible_cltv_offset ( ) {
5077
+ let ( secp_ctx, network, _, _, logger) = build_graph ( ) ;
5078
+ let ( _, our_id, _, nodes) = get_nodes ( & secp_ctx) ;
5079
+ let network_graph = network. read_only ( ) ;
5080
+ let network_nodes = network_graph. nodes ( ) ;
5081
+ let network_channels = network_graph. channels ( ) ;
5082
+ let scorer = test_utils:: TestScorer :: with_penalty ( 0 ) ;
5083
+ let payment_params = PaymentParameters :: from_node_id ( nodes[ 3 ] ) ;
5084
+ let keys_manager = test_utils:: TestKeysInterface :: new ( & [ 4u8 ; 32 ] , Network :: Testnet ) ;
5085
+ let random_seed_bytes = keys_manager. get_secure_random_bytes ( ) ;
5086
+
5087
+ let mut route = get_route ( & our_id, & payment_params, & network, None , 100 , 0 ,
5088
+ Arc :: clone ( & logger) , & scorer, & random_seed_bytes) . unwrap ( ) ;
5089
+ add_random_cltv_offset ( & mut route, & payment_params, & network, & random_seed_bytes) ;
5090
+
5091
+ let mut path_plausibility = vec ! [ ] ;
5092
+
5093
+ for p in route. paths {
5094
+ // 1. Select random observation point
5095
+ let mut prng = ChaCha20 :: new ( & random_seed_bytes, & [ 0u8 ; 12 ] ) ;
5096
+ let mut random_bytes = [ 0u8 ; :: core:: mem:: size_of :: < usize > ( ) ] ;
5097
+
5098
+ prng. process_in_place ( & mut random_bytes) ;
5099
+ let random_path_index = usize:: from_be_bytes ( random_bytes) . wrapping_rem ( p. len ( ) ) ;
5100
+ let observation_point = NodeId :: from_pubkey ( & p. get ( random_path_index) . unwrap ( ) . pubkey ) ;
5101
+
5102
+ // 2. Calculate what CLTV expiry delta we would observe there
5103
+ let observed_cltv_expiry_delta: u32 = p[ random_path_index..] . iter ( ) . map ( |h| h. cltv_expiry_delta ) . sum ( ) ;
5104
+
5105
+ // 3. Starting from the observation point, find candidate paths
5106
+ let mut candidates: VecDeque < ( NodeId , Vec < u32 > ) > = VecDeque :: new ( ) ;
5107
+ candidates. push_back ( ( observation_point, vec ! [ ] ) ) ;
5108
+
5109
+ let mut found_plausible_candidate = false ;
5110
+
5111
+ ' candidate_loop: while let Some ( ( cur_node_id, cur_path_cltv_deltas) ) = candidates. pop_front ( ) {
5112
+ if let Some ( remaining) = observed_cltv_expiry_delta. checked_sub ( cur_path_cltv_deltas. iter ( ) . sum :: < u32 > ( ) ) {
5113
+ if remaining == 0 || remaining. wrapping_rem ( 40 ) == 0 || remaining. wrapping_rem ( 144 ) == 0 {
5114
+ found_plausible_candidate = true ;
5115
+ break ' candidate_loop;
5116
+ }
5117
+ }
5118
+
5119
+ if let Some ( cur_node) = network_nodes. get ( & cur_node_id) {
5120
+ for channel_id in & cur_node. channels {
5121
+ if let Some ( channel_info) = network_channels. get ( & channel_id) {
5122
+ if let Some ( ( dir_info, next_id) ) = channel_info. as_directed_from ( & cur_node_id) {
5123
+ if let Some ( channel_update_info) = dir_info. direction ( ) {
5124
+ let next_cltv_expiry_delta = channel_update_info. cltv_expiry_delta as u32 ;
5125
+ if cur_path_cltv_deltas. iter ( ) . sum :: < u32 > ( )
5126
+ . saturating_add ( next_cltv_expiry_delta) <= observed_cltv_expiry_delta {
5127
+ let mut new_path_cltv_deltas = cur_path_cltv_deltas. clone ( ) ;
5128
+ new_path_cltv_deltas. push ( next_cltv_expiry_delta) ;
5129
+ candidates. push_back ( ( * next_id, new_path_cltv_deltas) ) ;
5130
+ }
5131
+ }
5132
+ }
5133
+ }
5134
+ }
5135
+ }
5136
+ }
5137
+
5138
+ path_plausibility. push ( found_plausible_candidate) ;
5139
+ }
5140
+ assert ! ( path_plausibility. iter( ) . all( |x| * x) ) ;
5141
+ }
5142
+
5074
5143
#[ cfg( not( feature = "no-std" ) ) ]
5075
5144
pub ( super ) fn random_init_seed ( ) -> u64 {
5076
5145
// Because the default HashMap in std pulls OS randomness, we can use it as a (bad) RNG.
0 commit comments