Skip to content

Commit 4677e14

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 c575429 commit 4677e14

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),
@@ -952,6 +964,13 @@ impl NetworkGraph {
952964
},
953965
};
954966

967+
#[allow(unused_mut, unused_assignments)]
968+
let mut announcement_received_time = 0;
969+
#[cfg(feature = "std")]
970+
{
971+
announcement_received_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs();
972+
}
973+
955974
let chan_info = ChannelInfo {
956975
features: msg.features.clone(),
957976
node_one: NodeId::from_pubkey(&msg.node_id_1),
@@ -961,6 +980,7 @@ impl NetworkGraph {
961980
capacity_sats: utxo_value,
962981
announcement_message: if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY
963982
{ full_msg.cloned() } else { None },
983+
announcement_received_time,
964984
};
965985

966986
let mut channels = self.channels.write().unwrap();
@@ -1045,6 +1065,67 @@ impl NetworkGraph {
10451065
}
10461066
}
10471067

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

1334+
use super::STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS;
1335+
12531336
use bitcoin::hashes::sha256d::Hash as Sha256dHash;
12541337
use bitcoin::hashes::Hash;
12551338
use bitcoin::network::constants::Network;
@@ -1733,28 +1816,73 @@ mod tests {
17331816
}
17341817

17351818
// Permanent closing deletes a channel
1819+
net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
1820+
payment_id: None,
1821+
payment_hash: PaymentHash([0; 32]),
1822+
rejected_by_dest: false,
1823+
all_paths_failed: true,
1824+
path: vec![],
1825+
network_update: Some(NetworkUpdate::ChannelClosed {
1826+
short_channel_id,
1827+
is_permanent: true,
1828+
}),
1829+
short_channel_id: None,
1830+
retry: None,
1831+
error_code: None,
1832+
error_data: None,
1833+
});
1834+
1835+
assert_eq!(network_graph.read_only().channels().len(), 0);
1836+
// Nodes are also deleted because there are no associated channels anymore
1837+
assert_eq!(network_graph.read_only().nodes().len(), 0);
1838+
// TODO: Test NetworkUpdate::NodeFailure, which is not implemented yet.
1839+
}
1840+
1841+
#[test]
1842+
fn test_channel_timeouts() {
1843+
// Test the removal of channels with `remove_stale_channels`.
1844+
let logger = test_utils::TestLogger::new();
1845+
let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Testnet));
1846+
let genesis_hash = genesis_block(Network::Testnet).header.block_hash();
1847+
let network_graph = NetworkGraph::new(genesis_hash);
1848+
let net_graph_msg_handler = NetGraphMsgHandler::new(&network_graph, Some(chain_source.clone()), &logger);
1849+
let secp_ctx = Secp256k1::new();
1850+
1851+
let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap();
1852+
let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap();
1853+
1854+
let valid_channel_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx);
1855+
let short_channel_id = valid_channel_announcement.contents.short_channel_id;
1856+
let chain_source: Option<&test_utils::TestChainSource> = None;
1857+
assert!(network_graph.update_channel_from_announcement(&valid_channel_announcement, &chain_source, &secp_ctx).is_ok());
1858+
assert!(network_graph.read_only().channels().get(&short_channel_id).is_some());
1859+
1860+
let valid_channel_update = get_signed_channel_update(|_| {}, node_1_privkey, &secp_ctx);
1861+
assert!(net_graph_msg_handler.handle_channel_update(&valid_channel_update).is_ok());
1862+
assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_some());
1863+
1864+
network_graph.remove_stale_channels_with_time(100 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS);
1865+
assert_eq!(network_graph.read_only().channels().len(), 1);
1866+
assert_eq!(network_graph.read_only().nodes().len(), 2);
1867+
1868+
network_graph.remove_stale_channels_with_time(101 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS);
1869+
#[cfg(feature = "std")]
17361870
{
1737-
net_graph_msg_handler.handle_event(&Event::PaymentPathFailed {
1738-
payment_id: None,
1739-
payment_hash: PaymentHash([0; 32]),
1740-
rejected_by_dest: false,
1741-
all_paths_failed: true,
1742-
path: vec![],
1743-
network_update: Some(NetworkUpdate::ChannelClosed {
1744-
short_channel_id,
1745-
is_permanent: true,
1746-
}),
1747-
short_channel_id: None,
1748-
retry: None,
1749-
error_code: None,
1750-
error_data: None,
1751-
});
1871+
// In std mode, a further check is performed before fully removing the channel -
1872+
// the channel_announcement must have been received at least two weeks ago. We
1873+
// fudge that here by indicating the time has jumped two weeks. Note that the
1874+
// directional channel information will have been removed already..
1875+
assert_eq!(network_graph.read_only().channels().len(), 1);
1876+
assert_eq!(network_graph.read_only().nodes().len(), 2);
1877+
assert!(network_graph.read_only().channels().get(&short_channel_id).unwrap().one_to_two.is_none());
17521878

1753-
assert_eq!(network_graph.read_only().channels().len(), 0);
1754-
// Nodes are also deleted because there are no associated channels anymore
1755-
assert_eq!(network_graph.read_only().nodes().len(), 0);
1879+
use std::time::{SystemTime, UNIX_EPOCH};
1880+
let announcement_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs();
1881+
network_graph.remove_stale_channels_with_time(announcement_time + 1 + STALE_CHANNEL_UPDATE_AGE_LIMIT_SECS);
17561882
}
1757-
// TODO: Test NetworkUpdate::NodeFailure, which is not implemented yet.
1883+
1884+
assert_eq!(network_graph.read_only().channels().len(), 0);
1885+
assert_eq!(network_graph.read_only().nodes().len(), 0);
17581886
}
17591887

17601888
#[test]

0 commit comments

Comments
 (0)