Skip to content

Commit 20a93fc

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 c76dbc7 commit 20a93fc

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
///
@@ -1639,8 +1701,7 @@ mod tests {
16391701
};
16401702
}
16411703

1642-
#[test]
1643-
fn handling_network_update() {
1704+
fn do_handling_network_update(remove_by_timeout: bool) {
16441705
let logger = test_utils::TestLogger::new();
16451706
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
16461707
let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
@@ -1719,8 +1780,13 @@ mod tests {
17191780
};
17201781
}
17211782

1722-
// Permanent closing deletes a channel
1723-
{
1783+
if remove_by_timeout {
1784+
network_graph.remove_stale_channels_with_time(100 + 60 * 60 * 14);
1785+
assert_eq!(network_graph.read_only().channels().len(), 1);
1786+
assert_eq!(network_graph.read_only().nodes().len(), 2);
1787+
network_graph.remove_stale_channels_with_time(101 + 60 * 60 * 14);
1788+
} else {
1789+
// Permanent closing deletes a channel
17241790
net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
17251791
payment_id: None,
17261792
payment_hash: PaymentHash([0; 32]),
@@ -1736,14 +1802,20 @@ mod tests {
17361802
error_code: None,
17371803
error_data: None,
17381804
});
1739-
1740-
assert_eq!(network_graph.read_only().channels().len(), 0);
1741-
// Nodes are also deleted because there are no associated channels anymore
1742-
assert_eq!(network_graph.read_only().nodes().len(), 0);
17431805
}
1806+
1807+
assert_eq!(network_graph.read_only().channels().len(), 0);
1808+
// Nodes are also deleted because there are no associated channels anymore
1809+
assert_eq!(network_graph.read_only().nodes().len(), 0);
17441810
// TODO: Test NetworkUpdate::NodeFailure, which is not implemented yet.
17451811
}
17461812

1813+
#[test]
1814+
fn handling_network_update() {
1815+
do_handling_network_update(true);
1816+
do_handling_network_update(false);
1817+
}
1818+
17471819
#[test]
17481820
fn getting_next_channel_announcements() {
17491821
let network_graph = create_network_graph();

0 commit comments

Comments
 (0)