Ignore channel_update with dont_forward bit set in P2PGossipSync
What changed, and why it matters
This change makes the Lightning node ignore certain channel update messages that carry a 'do not forward' flag. Previously, the node may have accepted and stored these private-channel updates in its public network map. The fix prevents private channel information from being treated as public routing data, which could leak channel details or pollute the routing graph.
Adopt the patch to ensure private channel updates are not ingested or relayed. Monitor for any related routing inconsistencies in deployments that previously accepted such updates.
Security signals we found
Privacy leak prevention: stops private channel updates from entering the public network graph
Protocol compliance: enforces BOLT 7 dont_forward semantics for channel_update
Graph pollution prevention: avoids storing routing data that should not be relayed
No cryptographic bypass or memory safety issue evident
Evidence from the diff
In P2PGossipSync::handle_channel_update, the patch adds an early check for the dont_forward bit (bit 1 of message_flags per BOLT 7). If set, it returns a LightningError with ErrorAction::IgnoreAndLog(Level::Debug) and does not call network_graph.update_channel. A test verifies that such updates are rejected and not stored in the NetworkGraph.
Changed components
lightning/src/routing/gossip.rsP2PGossipSyncNetworkGraph channel update handlingInspect captured patch +67 / −0
diff --git a/lightning/src/routing/gossip.rs b/lightning/src/routing/gossip.rs
index 534bebe..29053bb 100644
--- a/lightning/src/routing/gossip.rs
+++ b/lightning/src/routing/gossip.rs
@@ -568,6 +568,14 @@ where
fn handle_channel_update(
&self, _their_node_id: Option<PublicKey>, msg: &msgs::ChannelUpdate,
) -> Result<Option<(NodeId, NodeId)>, LightningError> {
+ // Ignore channel updates with dont_forward bit set - these are for private channels
+ // and shouldn't be gossiped or stored in the network graph
+ if msg.contents.message_flags & (1 << 1) != 0 {
+ return Err(LightningError {
+ err: "Ignoring channel_update with dont_forward bit set".to_owned(),
+ action: ErrorAction::IgnoreAndLog(Level::Debug),
+ });
+ }
match self.network_graph.update_channel(msg) {
Ok(nodes) if msg.contents.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY => Ok(nodes),
Ok(_) => Ok(None),
@@ -3341,6 +3349,65 @@ pub(crate) mod tests {
};
}
+ #[test]
+ fn handling_channel_update_with_dont_forward_flag() {
+ // Test that channel updates with the dont_forward bit set are rejected
+ let secp_ctx = Secp256k1::new();
+ let logger = test_utils::TestLogger::new();
+ let chain_source = test_utils::TestChainSource::new(Network::Testnet);
+ let network_graph = NetworkGraph::new(Network::Testnet, &logger);
+ let gossip_sync = P2PGossipSync::new(&network_graph, Some(&chain_source), &logger);
+
+ let node_1_privkey = &SecretKey::from_slice(&[42; 32]).unwrap();
+ let node_1_pubkey = PublicKey::from_secret_key(&secp_ctx, node_1_privkey);
+ let node_2_privkey = &SecretKey::from_slice(&[41; 32]).unwrap();
+
+ // First announce a channel so we have something to update
+ let good_script = get_channel_script(&secp_ctx);
+ *chain_source.utxo_ret.lock().unwrap() = UtxoResult::Sync(Ok(TxOut {
+ value: Amount::from_sat(1000_000),
+ script_pubkey: good_script.clone(),
+ }));
+
+ let valid_channel_announcement =
+ get_signed_channel_announcement(|_| {}, node_1_privkey, node_2_privkey, &secp_ctx);
+ gossip_sync
+ .handle_channel_announcement(Some(node_1_pubkey), &valid_channel_announcement)
+ .unwrap();
+
+ // Create a channel update with dont_forward bit set (bit 1 of message_flags)
+ let dont_forward_update = get_signed_channel_update(
+ |unsigned_channel_update| {
+ unsigned_channel_update.message_flags = 1 | (1 << 1); // must_be_one + dont_forward
+ },
+ node_1_privkey,
+ &secp_ctx,
+ );
+
+ // The update should be rejected because dont_forward is set
+ match gossip_sync.handle_channel_update(Some(node_1_pubkey), &dont_forward_update) {
+ Ok(_) => panic!("Expected channel update with dont_forward to be rejected"),
+ Err(e) => {
+ assert_eq!(e.err, "Ignoring channel_update with dont_forward bit set");
+ match e.action {
+ crate::ln::msgs::ErrorAction::IgnoreAndLog(level) => {
+ assert_eq!(level, crate::util::logger::Level::Debug)
+ },
+ _ => panic!("Expected IgnoreAndLog action"),
+ }
+ },
+ };
+
+ // Verify the update was not applied to the network graph
+ let channels = network_graph.read_only();
+ let channel =
+ channels.channels().get(&valid_channel_announcement.contents.short_channel_id).unwrap();
+ assert!(
+ channel.one_to_two.is_none(),
+ "Channel update with dont_forward should not be stored in network graph"
+ );
+ }
+
#[test]
fn handling_network_update() {
let logger = test_utils::TestLogger::new();
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.