Skip to content

Commit 99c4685

Browse files
committed
Add a method to prune stale channels from NetworkGraph
We define "stale" as "haven't heard an updated channel_update in two weeks", as described in BOLT 7. We also filter out stale channels at write-time for `std` users, as we can look up the current time.
1 parent 4bb7e30 commit 99c4685

File tree

1 file changed

+80
-8
lines changed

1 file changed

+80
-8
lines changed

lightning/src/routing/network_graph.rs

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,11 @@ const MIN_SERIALIZATION_VERSION: u8 = 1;
730730

731731
impl Writeable for NetworkGraph {
732732
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
733+
#[cfg(feature = "std")]
734+
{
735+
self.remove_stale_channels();
736+
}
737+
733738
write_ver_prefix!(writer, SERIALIZATION_VERSION, MIN_SERIALIZATION_VERSION);
734739

735740
self.genesis_hash.write(writer)?;
@@ -1040,6 +1045,63 @@ impl NetworkGraph {
10401045
}
10411046
}
10421047

1048+
#[cfg(feature = "std")]
1049+
/// Removes information about channels which we haven't heard any updates about in some time.
1050+
/// This can be used regularly to prune the network graph from channels which likely no longer
1051+
/// exist.
1052+
///
1053+
/// While there is no formal requirement that nodes regularly re-broadcast their channel
1054+
/// updates every two weeks, the non-normative section of BOLT 7 currently suggests that
1055+
/// pruning occurrs for updates which are at least two weeks old, which we implement here.
1056+
///
1057+
/// This method is automatically called immediately before writing the network graph via
1058+
/// [`Writeable::write`].
1059+
///
1060+
/// This method is only available with the `std` feature. See
1061+
/// [`NetworkGraph::remove_stale_channels_with_time`] for `no-std` use.
1062+
pub fn remove_stale_channels(&self) {
1063+
use std::time::{SystemTime, UNIX_EPOCH};
1064+
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs();
1065+
self.remove_stale_channels_with_time(time);
1066+
}
1067+
1068+
/// Removes information about channels which we haven't heard any updates about in some time.
1069+
/// This can be used regularly to prune the network graph from channels which likely no longer
1070+
/// exist.
1071+
///
1072+
/// While there is no formal requirement that nodes regularly re-broadcast their channel
1073+
/// updates every two weeks, the non-normative section of BOLT 7 currently suggests that
1074+
/// pruning occurrs for updates which are at least two weeks old, which we implement here.
1075+
///
1076+
/// This function takes the current unix time as an argument. For users with the `std` feature
1077+
/// enabled, [`NetworkGraph::remove_stale_channels`] may be preferrable.
1078+
pub fn remove_stale_channels_with_time(&self, current_time_unix: u64) {
1079+
let mut channels = self.channels.write().unwrap();
1080+
// Time out if we haven't received an update in at least 14 days.
1081+
let min_time_unix: u32 = (current_time_unix - 60 * 60 * 14) as u32;
1082+
// Sadly BTreeMap::retain was only stabilized in 1.53 so we can't switch to it for some
1083+
// time.
1084+
let mut scids_to_remove = Vec::new();
1085+
for (scid, info) in channels.iter_mut() {
1086+
if info.one_to_two.is_some() && info.one_to_two.as_ref().unwrap().last_update < min_time_unix {
1087+
info.one_to_two = None;
1088+
}
1089+
if info.two_to_one.is_some() && info.two_to_one.as_ref().unwrap().last_update < min_time_unix {
1090+
info.two_to_one = None;
1091+
}
1092+
if info.one_to_two.is_none() && info.two_to_one.is_none() {
1093+
scids_to_remove.push(*scid);
1094+
}
1095+
}
1096+
if !scids_to_remove.is_empty() {
1097+
let mut nodes = self.nodes.write().unwrap();
1098+
for scid in scids_to_remove {
1099+
let info = channels.remove(&scid).expect("We just accessed this scid, it should be present");
1100+
Self::remove_channel_in_nodes(&mut nodes, &info, scid);
1101+
}
1102+
}
1103+
}
1104+
10431105
/// For an already known (from announcement) channel, update info about one of the directions
10441106
/// of the channel.
10451107
///
@@ -1737,8 +1799,7 @@ mod tests {
17371799

17381800
}
17391801

1740-
#[test]
1741-
fn handling_network_update() {
1802+
fn do_handling_network_update(remove_by_timeout: bool) {
17421803
let logger = test_utils::TestLogger::new();
17431804
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
17441805
let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
@@ -1857,8 +1918,13 @@ mod tests {
18571918
};
18581919
}
18591920

1860-
// Permanent closing deletes a channel
1861-
{
1921+
if remove_by_timeout {
1922+
network_graph.remove_stale_channels_with_time(100 + 60 * 60 * 14);
1923+
assert_eq!(network_graph.read_only().channels().len(), 1);
1924+
assert_eq!(network_graph.read_only().nodes().len(), 1);
1925+
network_graph.remove_stale_channels_with_time(101 + 60 * 60 * 14);
1926+
} else {
1927+
// Permanent closing deletes a channel
18621928
net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
18631929
payment_id: None,
18641930
payment_hash: PaymentHash([0; 32]),
@@ -1874,14 +1940,20 @@ mod tests {
18741940
error_code: None,
18751941
error_data: None,
18761942
});
1877-
1878-
assert_eq!(network_graph.read_only().channels().len(), 0);
1879-
// Nodes are also deleted because there are no associated channels anymore
1880-
assert_eq!(network_graph.read_only().nodes().len(), 0);
18811943
}
1944+
1945+
assert_eq!(network_graph.read_only().channels().len(), 0);
1946+
// Nodes are also deleted because there are no associated channels anymore
1947+
assert_eq!(network_graph.read_only().nodes().len(), 0);
18821948
// TODO: Test NetworkUpdate::NodeFailure, which is not implemented yet.
18831949
}
18841950

1951+
#[test]
1952+
fn handling_network_update() {
1953+
do_handling_network_update(true);
1954+
do_handling_network_update(false);
1955+
}
1956+
18851957
#[test]
18861958
fn getting_next_channel_announcements() {
18871959
let network_graph = create_network_graph();

0 commit comments

Comments
 (0)