Skip to content

Commit a19a01e

Browse files
committed
Avoid redundant {channel,node}_announcement signature checks
If we receive `{channel,node}_announcement` messages which we already have, we first validate their signatures and then look in our graph and discover that we should discard the messages. This avoids a second lock in `node_announcement` handling but does not impact our locking in `channel_announcement` handling. It also avoids lock contention in cases where the signatures are invalid, but that should be exceedingly rare. For nodes with relatively few peers, this is a fine state to be in, however for nodes with many peers, we may see the same messages hundreds of times. This causes a rather substantial waste of CPU resources validating gossip messages. Instead, here, we change to checking our network graph first and then validate the signatures only if we don't already have the message.
1 parent d35239c commit a19a01e

File tree

1 file changed

+55
-33
lines changed

1 file changed

+55
-33
lines changed

lightning/src/routing/gossip.rs

Lines changed: 55 additions & 33 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) = nodes.self.nodes.read().unwrap().get(&msg.node_id) {
1726+
if let Some(node_info) = node.announcement_info.as_ref() {
1727+
if node_info.last_update() == msg.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.contents, utxo_lookup)?;
18201831
self.update_channel_from_unsigned_announcement_intern(msg, None, utxo_lookup)
18211832
}
18221833

@@ -1911,6 +1922,50 @@ 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+
1965+
/// Update channel information from a received announcement.
1966+
///
1967+
/// Generally [`Self::pre_channel_announcement_validation_check`] should have been called
1968+
/// first.
19141969
fn update_channel_from_unsigned_announcement_intern<U: Deref>(
19151970
&self, msg: &msgs::UnsignedChannelAnnouncement, full_msg: Option<&msgs::ChannelAnnouncement>, utxo_lookup: &Option<U>
19161971
) -> Result<(), LightningError>
@@ -1928,39 +1983,6 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
19281983
});
19291984
}
19301985

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-
19641986
{
19651987
let removed_channels = self.removed_channels.lock().unwrap();
19661988
let removed_nodes = self.removed_nodes.lock().unwrap();

0 commit comments

Comments
 (0)