Skip to content

Commit 13505d8

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 13505d8

File tree

1 file changed

+147
-19
lines changed

1 file changed

+147
-19
lines changed

lightning/src/routing/network_graph.rs

Lines changed: 147 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ use sync::Mutex;
4343
use core::ops::Deref;
4444
use bitcoin::hashes::hex::ToHex;
4545

46+
#[cfg(feature = "std")]
47+
use std::time::{SystemTime, UNIX_EPOCH};
48+
49+
/// We remove stale channel directional info two weeks after the last update, per BOLT 7's
50+
/// suggestion.
51+
const STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS: u64 = 60 * 60 * 24 * 14;
52+
4653
/// The maximum number of extra bytes which we do not understand in a gossip message before we will
4754
/// refuse to relay the message.
4855
const MAX_EXCESS_BYTES_FOR_RELAY: usize = 1024;
@@ -628,6 +635,10 @@ pub struct ChannelInfo {
628635
/// Everything else is useful only for sending out for initial routing sync.
629636
/// Not stored if contains excess data to prevent DoS.
630637
pub announcement_message: Option<ChannelAnnouncement>,
638+
/// The timestamp when we received the announcement, if we are running with feature = "std"
639+
/// (which we can probably assume we are - no-std environments probably won't have a full
640+
/// network graph in memory!).
641+
announcement_received_time: u64,
631642
}
632643

633644
impl fmt::Display for ChannelInfo {
@@ -640,6 +651,7 @@ impl fmt::Display for ChannelInfo {
640651

641652
impl_writeable_tlv_based!(ChannelInfo, {
642653
(0, features, required),
654+
(1, announcement_received_time, (default_value, 0)),
643655
(2, node_one, required),
644656
(4, one_to_two, required),
645657
(6, node_two, required),
@@ -947,6 +959,13 @@ impl NetworkGraph {
947959
},
948960
};
949961

962+
#[allow(unused_mut, unused_assignments)]
963+
let mut announcement_received_time = 0;
964+
#[cfg(feature = "std")]
965+
{
966+
announcement_received_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs();
967+
}
968+
950969
let chan_info = ChannelInfo {
951970
features: msg.features.clone(),
952971
node_one: NodeId::from_pubkey(&msg.node_id_1),
@@ -956,6 +975,7 @@ impl NetworkGraph {
956975
capacity_sats: utxo_value,
957976
announcement_message: if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY
958977
{ full_msg.cloned() } else { None },
978+
announcement_received_time,
959979
};
960980

961981
let mut channels = self.channels.write().unwrap();
@@ -1040,6 +1060,67 @@ impl NetworkGraph {
10401060
}
10411061
}
10421062

1063+
#[cfg(feature = "std")]
1064+
/// Removes information about channels that we haven't heard any updates about in some time.
1065+
/// This can be used regularly to prune the network graph of channels that likely no longer
1066+
/// exist.
1067+
///
1068+
/// While there is no formal requirement that nodes regularly re-broadcast their channel
1069+
/// updates every two weeks, the non-normative section of BOLT 7 currently suggests that
1070+
/// pruning occur for updates which are at least two weeks old, which we implement here.
1071+
///
1072+
///
1073+
/// This method is only available with the `std` feature. See
1074+
/// [`NetworkGraph::remove_stale_channels_with_time`] for `no-std` use.
1075+
pub fn remove_stale_channels(&self) {
1076+
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs();
1077+
self.remove_stale_channels_with_time(time);
1078+
}
1079+
1080+
/// Removes information about channels that we haven't heard any updates about in some time.
1081+
/// This can be used regularly to prune the network graph of channels that likely no longer
1082+
/// exist.
1083+
///
1084+
/// While there is no formal requirement that nodes regularly re-broadcast their channel
1085+
/// updates every two weeks, the non-normative section of BOLT 7 currently suggests that
1086+
/// pruning occur for updates which are at least two weeks old, which we implement here.
1087+
///
1088+
/// This function takes the current unix time as an argument. For users with the `std` feature
1089+
/// enabled, [`NetworkGraph::remove_stale_channels`] may be preferable.
1090+
pub fn remove_stale_channels_with_time(&self, current_time_unix: u64) {
1091+
let mut channels = self.channels.write().unwrap();
1092+
// Time out if we haven't received an update in at least 14 days.
1093+
if current_time_unix > u32::max_value() as u64 { return; } // Remove by 2106
1094+
if current_time_unix < STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS { return; }
1095+
let min_time_unix: u32 = (current_time_unix - STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS) as u32;
1096+
// Sadly BTreeMap::retain was only stabilized in 1.53 so we can't switch to it for some
1097+
// time.
1098+
let mut scids_to_remove = Vec::new();
1099+
for (scid, info) in channels.iter_mut() {
1100+
if info.one_to_two.is_some() && info.one_to_two.as_ref().unwrap().last_update < min_time_unix {
1101+
info.one_to_two = None;
1102+
}
1103+
if info.two_to_one.is_some() && info.two_to_one.as_ref().unwrap().last_update < min_time_unix {
1104+
info.two_to_one = None;
1105+
}
1106+
if info.one_to_two.is_none() && info.two_to_one.is_none() {
1107+
// We check the announcement_received_time here to ensure we don't drop
1108+
// announcements that we just received and are just waiting for our peer to send a
1109+
// channel_update for.
1110+
if info.announcement_received_time < min_time_unix as u64 {
1111+
scids_to_remove.push(*scid);
1112+
}
1113+
}
1114+
}
1115+
if !scids_to_remove.is_empty() {
1116+
let mut nodes = self.nodes.write().unwrap();
1117+
for scid in scids_to_remove {
1118+
let info = channels.remove(&scid).expect("We just accessed this scid, it should be present");
1119+
Self::remove_channel_in_nodes(&mut nodes, &info, scid);
1120+
}
1121+
}
1122+
}
1123+
10431124
/// For an already known (from announcement) channel, update info about one of the directions
10441125
/// of the channel.
10451126
///
@@ -1237,6 +1318,8 @@ mod tests {
12371318
use util::events::{Event, EventHandler, MessageSendEvent, MessageSendEventsProvider};
12381319
use util::scid_utils::scid_from_parts;
12391320

1321+
use super::STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS;
1322+
12401323
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
12411324
use bitcoin::hashes::Hash;
12421325
use bitcoin::network::constants::Network;
@@ -1720,28 +1803,73 @@ mod tests {
17201803
}
17211804

17221805
// Permanent closing deletes a channel
1806+
net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
1807+
payment_id: None,
1808+
payment_hash: PaymentHash([0; 32]),
1809+
rejected_by_dest: false,
1810+
all_paths_failed: true,
1811+
path: vec![],
1812+
network_update: Some(NetworkUpdate::ChannelClosed {
1813+
short_channel_id,
1814+
is_permanent: true,
1815+
}),
1816+
short_channel_id: None,
1817+
retry: None,
1818+
error_code: None,
1819+
error_data: None,
1820+
});
1821+
1822+
assert_eq!(network_graph.read_only().channels().len(), 0);
1823+
// Nodes are also deleted because there are no associated channels anymore
1824+
assert_eq!(network_graph.read_only().nodes().len(), 0);
1825+
// TODO: Test NetworkUpdate::NodeFailure, which is not implemented yet.
1826+
}
1827+
1828+
#[test]
1829+
fn test_channel_timeouts() {
1830+
// Test the removal of channels with `remove_stale_channels`.
1831+
let logger = test_utils::TestLogger::new();
1832+
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
1833+
let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
1834+
let network_graph = NetworkGraph::new(genesis_hash);
1835+
let net_graph_msg_handler = NetGraphMsgHandler::new(&network_graph, Some(chain_source.clone()), &logger);
1836+
let secp_ctx = Secp256k1::new();
1837+
1838+
let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap();
1839+
let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap();
1840+
1841+
let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx);
1842+
let short_channel_id = valid_channel_announcement.contents.short_channel_id;
1843+
let chain_source: Option<&test_utils::TestChainSource> = None;
1844+
assert!(network_graph.update_channel_from_announcement(&valid_channel_announcement, &chain_source, &secp_ctx).is_ok());
1845+
assert!(network_graph.read_only().channels().get(&short_channel_id).is_some());
1846+
1847+
let valid_channel_update = get_signed_channel_update(|_| {}, node_1_privkey, &secp_ctx);
1848+
assert!(net_graph_msg_handler.handle_channel_update(&valid_channel_update).is_ok());
1849+
assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_some());
1850+
1851+
network_graph.remove_stale_channels_with_time(100 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS);
1852+
assert_eq!(network_graph.read_only().channels().len(), 1);
1853+
assert_eq!(network_graph.read_only().nodes().len(), 2);
1854+
1855+
network_graph.remove_stale_channels_with_time(101 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS);
1856+
#[cfg(feature = "std")]
17231857
{
1724-
net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
1725-
payment_id: None,
1726-
payment_hash: PaymentHash([0; 32]),
1727-
rejected_by_dest: false,
1728-
all_paths_failed: true,
1729-
path: vec![],
1730-
network_update: Some(NetworkUpdate::ChannelClosed {
1731-
short_channel_id,
1732-
is_permanent: true,
1733-
}),
1734-
short_channel_id: None,
1735-
retry: None,
1736-
error_code: None,
1737-
error_data: None,
1738-
});
1858+
// In std mode, a further check is performed before fully removing the channel -
1859+
// the channel_announcement must have been received at least two weeks ago. We
1860+
// fudge that here by indicating the time has jumped two weeks. Note that the
1861+
// directional channel information will have been removed already..
1862+
assert_eq!(network_graph.read_only().channels().len(), 1);
1863+
assert_eq!(network_graph.read_only().nodes().len(), 2);
1864+
assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_none());
17391865

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);
1866+
use std::time::{SystemTime, UNIX_EPOCH};
1867+
let announcement_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs();
1868+
network_graph.remove_stale_channels_with_time(announcement_time + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS);
17431869
}
1744-
// TODO: Test NetworkUpdate::NodeFailure, which is not implemented yet.
1870+
1871+
assert_eq!(network_graph.read_only().channels().len(), 0);
1872+
assert_eq!(network_graph.read_only().nodes().len(), 0);
17451873
}
17461874

17471875
#[test]

0 commit comments

Comments
 (0)