@@ -1564,45 +1564,58 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
15641564 for path in route. paths . iter_mut ( ) {
15651565 let mut shadow_ctlv_expiry_delta_offset: u32 = 0 ;
15661566
1567- // Choose the last publicly known node as the starting point for the random walk
1568- if let Some ( starting_hop) = path. iter ( ) . rev ( ) . find ( |h| network_nodes. contains_key ( & NodeId :: from_pubkey ( & h. pubkey ) ) ) {
1569- let mut cur_node_id = NodeId :: from_pubkey ( & starting_hop. pubkey ) ;
1567+ // Remember the last three nodes of the random walk and avoid looping back on them.
1568+ // Init with the last three nodes from the actual path, if possible.
1569+ let mut nodes_to_avoid: [ NodeId ; 3 ] = [ NodeId :: from_pubkey ( & path. last ( ) . unwrap ( ) . pubkey ) ,
1570+ NodeId :: from_pubkey ( & path. get ( path. len ( ) . saturating_sub ( 2 ) ) . unwrap ( ) . pubkey ) ,
1571+ NodeId :: from_pubkey ( & path. get ( path. len ( ) . saturating_sub ( 3 ) ) . unwrap ( ) . pubkey ) ] ;
1572+
1573+ // Choose the last publicly known node as the starting point for the random walk.
1574+ let mut cur_hop: Option < NodeId > = None ;
1575+ let mut path_nonce = [ 0u8 ; 12 ] ;
1576+ if let Some ( starting_hop) = path. iter ( ) . rev ( )
1577+ . find ( |h| network_nodes. contains_key ( & NodeId :: from_pubkey ( & h. pubkey ) ) ) {
1578+ cur_hop = Some ( NodeId :: from_pubkey ( & starting_hop. pubkey ) ) ;
1579+ path_nonce. copy_from_slice ( & cur_hop. unwrap ( ) . as_slice ( ) [ ..12 ] ) ;
1580+ }
1581+
1582+ // Init PRNG with the path-dependant nonce, which is static for private paths.
1583+ let mut prng = ChaCha20 :: new ( random_seed_bytes, & path_nonce) ;
1584+ let mut random_path_bytes = [ 0u8 ; :: core:: mem:: size_of :: < usize > ( ) ] ;
15701585
1571- // Init PRNG with path nonce
1572- let mut path_nonce = [ 0u8 ; 12 ] ;
1573- path_nonce. copy_from_slice ( & cur_node_id. as_slice ( ) [ ..12 ] ) ;
1574- let mut prng = ChaCha20 :: new ( random_seed_bytes, & path_nonce) ;
1575- let mut random_path_bytes = [ 0u8 ; :: core:: mem:: size_of :: < usize > ( ) ] ;
1586+ // Pick a random path length in [1 .. 3]
1587+ prng. process_in_place ( & mut random_path_bytes) ;
1588+ let random_walk_length = usize:: from_be_bytes ( random_path_bytes) . wrapping_rem ( 3 ) . wrapping_add ( 1 ) ;
15761589
1577- // Pick a random path length in [1 .. 3]
1578- prng. process_in_place ( & mut random_path_bytes) ;
1579- let random_walk_length = usize:: from_be_bytes ( random_path_bytes) . wrapping_rem ( 3 ) . wrapping_add ( 1 ) ;
1590+ for random_hop in 0 ..random_walk_length {
1591+ // If we don't find a suitable offset in the public network graph, we default to
1592+ // MEDIAN_HOP_CLTV_EXPIRY_DELTA.
1593+ let mut random_hop_offset = MEDIAN_HOP_CLTV_EXPIRY_DELTA ;
15801594
1581- for _random_hop in 0 ..random_walk_length {
1595+ if let Some ( cur_node_id ) = cur_hop {
15821596 if let Some ( cur_node) = network_nodes. get ( & cur_node_id) {
1583- // Randomly choose the next hop
1597+ // Randomly choose the next unvisited hop.
15841598 prng. process_in_place ( & mut random_path_bytes) ;
1585- if let Some ( random_channel) = usize:: from_be_bytes ( random_path_bytes) . checked_rem ( cur_node. channels . len ( ) )
1599+ if let Some ( random_channel) = usize:: from_be_bytes ( random_path_bytes)
1600+ . checked_rem ( cur_node. channels . len ( ) )
15861601 . and_then ( |index| cur_node. channels . get ( index) )
15871602 . and_then ( |id| network_channels. get ( id) ) {
15881603 random_channel. as_directed_from ( & cur_node_id) . map ( |( dir_info, next_id) | {
1589- dir_info. direction ( ) . map ( |channel_update_info|
1590- shadow_ctlv_expiry_delta_offset = shadow_ctlv_expiry_delta_offset
1591- . checked_add ( channel_update_info. cltv_expiry_delta . into ( ) )
1592- . unwrap_or ( shadow_ctlv_expiry_delta_offset) ) ;
1593- cur_node_id = * next_id;
1604+ if !nodes_to_avoid. iter ( ) . any ( |x| x == next_id) {
1605+ nodes_to_avoid[ random_hop] = * next_id;
1606+ dir_info. direction ( ) . map ( |channel_update_info| {
1607+ random_hop_offset = channel_update_info. cltv_expiry_delta . into ( ) ;
1608+ cur_hop = Some ( * next_id) ;
1609+ } ) ;
1610+ }
15941611 } ) ;
15951612 }
15961613 }
15971614 }
1598- } else {
1599- // If the entire path is private, choose a random offset from multiples of
1600- // MEDIAN_HOP_CLTV_EXPIRY_DELTA
1601- let mut prng = ChaCha20 :: new ( random_seed_bytes, & [ 0u8 ; 8 ] ) ;
1602- let mut random_bytes = [ 0u8 ; 4 ] ;
1603- prng. process_in_place ( & mut random_bytes) ;
1604- let random_walk_length = u32:: from_be_bytes ( random_bytes) . wrapping_rem ( 3 ) . wrapping_add ( 1 ) ;
1605- shadow_ctlv_expiry_delta_offset = random_walk_length * MEDIAN_HOP_CLTV_EXPIRY_DELTA ;
1615+
1616+ shadow_ctlv_expiry_delta_offset = shadow_ctlv_expiry_delta_offset
1617+ . checked_add ( random_hop_offset)
1618+ . unwrap_or ( shadow_ctlv_expiry_delta_offset) ;
16061619 }
16071620
16081621 // Limit the total offset to reduce the worst-case locked liquidity timevalue
0 commit comments