@@ -1561,45 +1561,55 @@ fn add_random_cltv_offset(route: &mut Route, payment_params: &PaymentParameters,
1561
1561
for path in route. paths . iter_mut ( ) {
1562
1562
let mut shadow_ctlv_expiry_delta_offset: u32 = 0 ;
1563
1563
1564
- // Choose the last publicly known node as the starting point for the random walk
1565
- if let Some ( starting_hop) = path. iter ( ) . rev ( ) . find ( |h| network_nodes. contains_key ( & NodeId :: from_pubkey ( & h. pubkey ) ) ) {
1566
- let mut cur_node_id = NodeId :: from_pubkey ( & starting_hop. pubkey ) ;
1564
+ // Mark all nodes on the actual path as visited to avoid looping random walks.
1565
+ let mut visited_nodes: HashSet < NodeId > = HashSet :: new ( ) ;
1566
+ path. iter ( ) . for_each ( |h| { visited_nodes. insert ( NodeId :: from_pubkey ( & h. pubkey ) ) ; } ) ;
1567
+
1568
+ // Choose the last publicly known node as the starting point for the random walk.
1569
+ let mut cur_hop: Option < NodeId > = None ;
1570
+ let mut path_nonce = [ 0u8 ; 12 ] ;
1571
+ if let Some ( starting_hop) = path. iter ( ) . rev ( )
1572
+ . find ( |h| network_nodes. contains_key ( & NodeId :: from_pubkey ( & h. pubkey ) ) ) {
1573
+ cur_hop = Some ( NodeId :: from_pubkey ( & starting_hop. pubkey ) ) ;
1574
+ path_nonce. copy_from_slice ( & cur_hop. unwrap ( ) . as_slice ( ) [ ..12 ] ) ;
1575
+ }
1576
+
1577
+ // Init PRNG with the path-dependant nonce, which is static for private paths.
1578
+ let mut prng = ChaCha20 :: new ( random_seed_bytes, & path_nonce) ;
1579
+ let mut random_path_bytes = [ 0u8 ; :: core:: mem:: size_of :: < usize > ( ) ] ;
1567
1580
1568
- // Init PRNG with path nonce
1569
- let mut path_nonce = [ 0u8 ; 12 ] ;
1570
- path_nonce. copy_from_slice ( & cur_node_id. as_slice ( ) [ ..12 ] ) ;
1571
- let mut prng = ChaCha20 :: new ( random_seed_bytes, & path_nonce) ;
1572
- let mut random_path_bytes = [ 0u8 ; :: core:: mem:: size_of :: < usize > ( ) ] ;
1581
+ // Pick a random path length in [1 .. 3]
1582
+ prng. process_in_place ( & mut random_path_bytes) ;
1583
+ let random_walk_length = usize:: from_be_bytes ( random_path_bytes) . wrapping_rem ( 3 ) . wrapping_add ( 1 ) ;
1573
1584
1574
- // Pick a random path length in [1 .. 3]
1575
- prng. process_in_place ( & mut random_path_bytes) ;
1576
- let random_walk_length = usize:: from_be_bytes ( random_path_bytes) . wrapping_rem ( 3 ) . wrapping_add ( 1 ) ;
1585
+ for _random_hop in 0 ..random_walk_length {
1586
+ // If we don't find a suitable offset in the public network graph, we default to
1587
+ // MEDIAN_HOP_CLTV_EXPIRY_DELTA.
1588
+ let mut random_hop_offset = MEDIAN_HOP_CLTV_EXPIRY_DELTA ;
1577
1589
1578
- for _random_hop in 0 ..random_walk_length {
1590
+ if let Some ( cur_node_id ) = cur_hop {
1579
1591
if let Some ( cur_node) = network_nodes. get ( & cur_node_id) {
1580
- // Randomly choose the next hop
1592
+ // Randomly choose the next unvisited hop.
1581
1593
prng. process_in_place ( & mut random_path_bytes) ;
1582
- if let Some ( random_channel) = usize:: from_be_bytes ( random_path_bytes) . checked_rem ( cur_node. channels . len ( ) )
1594
+ if let Some ( random_channel) = usize:: from_be_bytes ( random_path_bytes)
1595
+ . checked_rem ( cur_node. channels . len ( ) )
1583
1596
. and_then ( |index| cur_node. channels . get ( index) )
1584
1597
. and_then ( |id| network_channels. get ( id) ) {
1585
1598
random_channel. as_directed_from ( & cur_node_id) . map ( |( dir_info, next_id) | {
1586
- dir_info. direction ( ) . map ( |channel_update_info|
1587
- shadow_ctlv_expiry_delta_offset = shadow_ctlv_expiry_delta_offset
1588
- . checked_add ( channel_update_info. cltv_expiry_delta . into ( ) )
1589
- . unwrap_or ( shadow_ctlv_expiry_delta_offset) ) ;
1590
- cur_node_id = * next_id;
1599
+ if visited_nodes. insert ( * next_id) {
1600
+ dir_info. direction ( ) . map ( |channel_update_info| {
1601
+ random_hop_offset = channel_update_info. cltv_expiry_delta . into ( ) ;
1602
+ cur_hop = Some ( * next_id) ;
1603
+ } ) ;
1604
+ }
1591
1605
} ) ;
1592
1606
}
1593
- }
1607
+ }
1594
1608
}
1595
- } else {
1596
- // If the entire path is private, choose a random offset from multiples of
1597
- // MEDIAN_HOP_CLTV_EXPIRY_DELTA
1598
- let mut prng = ChaCha20 :: new ( random_seed_bytes, & [ 0u8 ; 8 ] ) ;
1599
- let mut random_bytes = [ 0u8 ; 4 ] ;
1600
- prng. process_in_place ( & mut random_bytes) ;
1601
- let random_walk_length = u32:: from_be_bytes ( random_bytes) . wrapping_rem ( 3 ) . wrapping_add ( 1 ) ;
1602
- shadow_ctlv_expiry_delta_offset = random_walk_length * MEDIAN_HOP_CLTV_EXPIRY_DELTA ;
1609
+
1610
+ shadow_ctlv_expiry_delta_offset = shadow_ctlv_expiry_delta_offset
1611
+ . checked_add ( random_hop_offset)
1612
+ . unwrap_or ( shadow_ctlv_expiry_delta_offset) ;
1603
1613
}
1604
1614
1605
1615
// Limit the total offset to reduce the worst-case locked liquidity timevalue
0 commit comments