Skip to content

Commit a6dea2f

Browse files
Merge pull request #3305 from TheBlueMatt/2024-09-no-redundant-gossip-validation
Avoid redundant `{channel,node}_announcement` signature checks
2 parents 479654b + 56cb6a1 commit a6dea2f

File tree

1 file changed

+70
-45
lines changed

1 file changed

+70
-45
lines changed

lightning/src/routing/gossip.rs

Lines changed: 70 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,15 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
17201720
/// RoutingMessageHandler implementation to call it indirectly. This may be useful to accept
17211721
/// routing messages from a source using a protocol other than the lightning P2P protocol.
17221722
pub fn update_node_from_announcement(&self, msg: &msgs::NodeAnnouncement) -> Result<(), LightningError> {
1723+
// First check if we have the announcement already to avoid the CPU cost of validating a
1724+
// redundant announcement.
1725+
if let Some(node) = self.nodes.read().unwrap().get(&msg.contents.node_id) {
1726+
if let Some(node_info) = node.announcement_info.as_ref() {
1727+
if node_info.last_update() == msg.contents.timestamp {
1728+
return Err(LightningError{err: "Update had the same timestamp as last processed update".to_owned(), action: ErrorAction::IgnoreDuplicateGossip});
1729+
}
1730+
}
1731+
}
17231732
verify_node_announcement(msg, &self.secp_ctx)?;
17241733
self.update_node_from_announcement_intern(&msg.contents, Some(&msg))
17251734
}
@@ -1788,6 +1797,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
17881797
where
17891798
U::Target: UtxoLookup,
17901799
{
1800+
self.pre_channel_announcement_validation_check(&msg.contents, utxo_lookup)?;
17911801
verify_channel_announcement(msg, &self.secp_ctx)?;
17921802
self.update_channel_from_unsigned_announcement_intern(&msg.contents, Some(msg), utxo_lookup)
17931803
}
@@ -1817,6 +1827,7 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
18171827
where
18181828
U::Target: UtxoLookup,
18191829
{
1830+
self.pre_channel_announcement_validation_check(&msg, utxo_lookup)?;
18201831
self.update_channel_from_unsigned_announcement_intern(msg, None, utxo_lookup)
18211832
}
18221833

@@ -1911,6 +1922,52 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
19111922
Ok(())
19121923
}
19131924

1925+
/// If we already have all the information for a channel that we're gonna get, there's no
1926+
/// reason to redundantly process it.
1927+
///
1928+
/// In those cases, this will return an `Err` that we can return immediately. Otherwise it will
1929+
/// return an `Ok(())`.
1930+
fn pre_channel_announcement_validation_check<U: Deref>(
1931+
&self, msg: &msgs::UnsignedChannelAnnouncement, utxo_lookup: &Option<U>,
1932+
) -> Result<(), LightningError> where U::Target: UtxoLookup {
1933+
let channels = self.channels.read().unwrap();
1934+
1935+
if let Some(chan) = channels.get(&msg.short_channel_id) {
1936+
if chan.capacity_sats.is_some() {
1937+
// If we'd previously looked up the channel on-chain and checked the script
1938+
// against what appears on-chain, ignore the duplicate announcement.
1939+
//
1940+
// Because a reorg could replace one channel with another at the same SCID, if
1941+
// the channel appears to be different, we re-validate. This doesn't expose us
1942+
// to any more DoS risk than not, as a peer can always flood us with
1943+
// randomly-generated SCID values anyway.
1944+
//
1945+
// We use the Node IDs rather than the bitcoin_keys to check for "equivalence"
1946+
// as we didn't (necessarily) store the bitcoin keys, and we only really care
1947+
// if the peers on the channel changed anyway.
1948+
if msg.node_id_1 == chan.node_one && msg.node_id_2 == chan.node_two {
1949+
return Err(LightningError {
1950+
err: "Already have chain-validated channel".to_owned(),
1951+
action: ErrorAction::IgnoreDuplicateGossip
1952+
});
1953+
}
1954+
} else if utxo_lookup.is_none() {
1955+
// Similarly, if we can't check the chain right now anyway, ignore the
1956+
// duplicate announcement without bothering to take the channels write lock.
1957+
return Err(LightningError {
1958+
err: "Already have non-chain-validated channel".to_owned(),
1959+
action: ErrorAction::IgnoreDuplicateGossip
1960+
});
1961+
}
1962+
}
1963+
1964+
Ok(())
1965+
}
1966+
1967+
/// Update channel information from a received announcement.
1968+
///
1969+
/// Generally [`Self::pre_channel_announcement_validation_check`] should have been called
1970+
/// first.
19141971
fn update_channel_from_unsigned_announcement_intern<U: Deref>(
19151972
&self, msg: &msgs::UnsignedChannelAnnouncement, full_msg: Option<&msgs::ChannelAnnouncement>, utxo_lookup: &Option<U>
19161973
) -> Result<(), LightningError>
@@ -1928,39 +1985,6 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
19281985
});
19291986
}
19301987

1931-
{
1932-
let channels = self.channels.read().unwrap();
1933-
1934-
if let Some(chan) = channels.get(&msg.short_channel_id) {
1935-
if chan.capacity_sats.is_some() {
1936-
// If we'd previously looked up the channel on-chain and checked the script
1937-
// against what appears on-chain, ignore the duplicate announcement.
1938-
//
1939-
// Because a reorg could replace one channel with another at the same SCID, if
1940-
// the channel appears to be different, we re-validate. This doesn't expose us
1941-
// to any more DoS risk than not, as a peer can always flood us with
1942-
// randomly-generated SCID values anyway.
1943-
//
1944-
// We use the Node IDs rather than the bitcoin_keys to check for "equivalence"
1945-
// as we didn't (necessarily) store the bitcoin keys, and we only really care
1946-
// if the peers on the channel changed anyway.
1947-
if msg.node_id_1 == chan.node_one && msg.node_id_2 == chan.node_two {
1948-
return Err(LightningError {
1949-
err: "Already have chain-validated channel".to_owned(),
1950-
action: ErrorAction::IgnoreDuplicateGossip
1951-
});
1952-
}
1953-
} else if utxo_lookup.is_none() {
1954-
// Similarly, if we can't check the chain right now anyway, ignore the
1955-
// duplicate announcement without bothering to take the channels write lock.
1956-
return Err(LightningError {
1957-
err: "Already have non-chain-validated channel".to_owned(),
1958-
action: ErrorAction::IgnoreDuplicateGossip
1959-
});
1960-
}
1961-
}
1962-
}
1963-
19641988
{
19651989
let removed_channels = self.removed_channels.lock().unwrap();
19661990
let removed_nodes = self.removed_nodes.lock().unwrap();
@@ -2562,11 +2586,6 @@ pub(crate) mod tests {
25622586
};
25632587
}
25642588

2565-
match gossip_sync.handle_node_announcement(&valid_announcement) {
2566-
Ok(res) => assert!(res),
2567-
Err(_) => panic!()
2568-
};
2569-
25702589
let fake_msghash = hash_to_message!(zero_hash.as_byte_array());
25712590
match gossip_sync.handle_node_announcement(
25722591
&NodeAnnouncement {
@@ -2577,6 +2596,11 @@ pub(crate) mod tests {
25772596
Err(e) => assert_eq!(e.err, "Invalid signature on node_announcement message")
25782597
};
25792598

2599+
match gossip_sync.handle_node_announcement(&valid_announcement) {
2600+
Ok(res) => assert!(res),
2601+
Err(_) => panic!()
2602+
};
2603+
25802604
let announcement_with_data = get_signed_node_announcement(|unsigned_announcement| {
25812605
unsigned_announcement.timestamp += 1000;
25822606
unsigned_announcement.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0);
@@ -2698,23 +2722,24 @@ pub(crate) mod tests {
26982722
}
26992723
}
27002724

2701-
// Don't relay valid channels with excess data
2702-
let valid_announcement = get_signed_channel_announcement(|unsigned_announcement| {
2725+
let valid_excess_data_announcement = get_signed_channel_announcement(|unsigned_announcement| {
27032726
unsigned_announcement.short_channel_id += 4;
27042727
unsigned_announcement.excess_data.resize(MAX_EXCESS_BYTES_FOR_RELAY + 1, 0);
27052728
}, node_1_privkey, node_2_privkey, &secp_ctx);
2706-
match gossip_sync.handle_channel_announcement(&valid_announcement) {
2707-
Ok(res) => assert!(!res),
2708-
_ => panic!()
2709-
};
27102729

2711-
let mut invalid_sig_announcement = valid_announcement.clone();
2730+
let mut invalid_sig_announcement = valid_excess_data_announcement.clone();
27122731
invalid_sig_announcement.contents.excess_data = Vec::new();
27132732
match gossip_sync.handle_channel_announcement(&invalid_sig_announcement) {
27142733
Ok(_) => panic!(),
27152734
Err(e) => assert_eq!(e.err, "Invalid signature on channel_announcement message")
27162735
};
27172736

2737+
// Don't relay valid channels with excess data
2738+
match gossip_sync.handle_channel_announcement(&valid_excess_data_announcement) {
2739+
Ok(res) => assert!(!res),
2740+
_ => panic!()
2741+
};
2742+
27182743
let channel_to_itself_announcement = get_signed_channel_announcement(|_| {}, node_1_privkey, node_1_privkey, &secp_ctx);
27192744
match gossip_sync.handle_channel_announcement(&channel_to_itself_announcement) {
27202745
Ok(_) => panic!(),

0 commit comments

Comments
 (0)