@@ -1564,45 +1564,58 @@ 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
+ // 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 > ( ) ] ;
1570
1585
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 ) ;
1576
1589
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 ;
1580
1594
1581
- for _random_hop in 0 ..random_walk_length {
1595
+ if let Some ( cur_node_id ) = cur_hop {
1582
1596
if let Some ( cur_node) = network_nodes. get ( & cur_node_id) {
1583
- // Randomly choose the next hop
1597
+ // Randomly choose the next unvisited hop.
1584
1598
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 ( ) )
1586
1601
. and_then ( |index| cur_node. channels . get ( index) )
1587
1602
. and_then ( |id| network_channels. get ( id) ) {
1588
1603
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
+ }
1594
1611
} ) ;
1595
1612
}
1596
1613
}
1597
1614
}
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) ;
1606
1619
}
1607
1620
1608
1621
// Limit the total offset to reduce the worst-case locked liquidity timevalue
0 commit comments