Skip to content

Commit fcfd683

Browse files
authored
Merge pull request #1360 from tnull/2022-03-loopless-random-walks
Avoid looping CLTV shadow routes.
2 parents b010aeb + 11c3120 commit fcfd683

File tree

1 file changed

+40
-27
lines changed

1 file changed

+40
-27
lines changed

lightning/src/routing/router.rs

+40-27
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)