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