Skip to content

Commit 131849f

Browse files
committed
Validate channel_update signatures without holding a graph lock
We often process many gossip messages in parallel across different peer connections, making the `NetworkGraph` mutexes fairly contention-sensitive (not to mention the potential that we want to send a payment and need to find a path to do so). Because we need to look up a node's public key to validate a signature on `channel_update` messages, we always need to take a `NetworkGraph::channels` lock before we can validate the message. For simplicity, and to avoid taking a lock twice, we'd always validated the `channel_update` signature while holding the same lock, but here we address the contention issues by doing a `channel_update` validation in three stages. First we take a read lock on `NetworkGraph::channels` and check if the `channel_update` is new, then release the lock and validate the message signature, and finally take a write lock, (re-check if the `channel_update` is new) and update the graph.
1 parent 0b7838b commit 131849f

File tree

1 file changed

+57
-54
lines changed

1 file changed

+57
-54
lines changed

lightning/src/routing/gossip.rs

Lines changed: 57 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,62 +2294,65 @@ impl<L: Deref> NetworkGraph<L> where L::Target: Logger {
22942294
}
22952295
};
22962296

2297-
let mut channels = self.channels.write().unwrap();
2298-
match channels.get_mut(&msg.short_channel_id) {
2299-
None => {
2300-
core::mem::drop(channels);
2301-
self.pending_checks.check_hold_pending_channel_update(msg, full_msg)?;
2302-
return Err(LightningError {
2303-
err: "Couldn't find channel for update".to_owned(),
2304-
action: ErrorAction::IgnoreAndLog(Level::Gossip),
2305-
});
2306-
},
2307-
Some(channel) => {
2308-
check_msg_sanity(channel)?;
2309-
2310-
macro_rules! get_new_channel_info {
2311-
() => { {
2312-
let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY
2313-
{ full_msg.cloned() } else { None };
2314-
2315-
let updated_channel_update_info = ChannelUpdateInfo {
2316-
enabled: chan_enabled,
2317-
last_update: msg.timestamp,
2318-
cltv_expiry_delta: msg.cltv_expiry_delta,
2319-
htlc_minimum_msat: msg.htlc_minimum_msat,
2320-
htlc_maximum_msat: msg.htlc_maximum_msat,
2321-
fees: RoutingFees {
2322-
base_msat: msg.fee_base_msat,
2323-
proportional_millionths: msg.fee_proportional_millionths,
2324-
},
2325-
last_update_message
2326-
};
2327-
Some(updated_channel_update_info)
2328-
} }
2329-
}
2330-
2331-
let msg_hash = hash_to_message!(&message_sha256d_hash(&msg)[..]);
2332-
if msg.channel_flags & 1 == 1 {
2333-
if let Some(sig) = sig {
2334-
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_two.as_slice()).map_err(|_| LightningError{
2297+
let node_pubkey;
2298+
{
2299+
let channels = self.channels.read().unwrap();
2300+
match channels.get(&msg.short_channel_id) {
2301+
None => {
2302+
core::mem::drop(channels);
2303+
self.pending_checks.check_hold_pending_channel_update(msg, full_msg)?;
2304+
return Err(LightningError {
2305+
err: "Couldn't find channel for update".to_owned(),
2306+
action: ErrorAction::IgnoreAndLog(Level::Gossip),
2307+
});
2308+
},
2309+
Some(channel) => {
2310+
check_msg_sanity(channel)?;
2311+
let node_id = if msg.channel_flags & 1 == 1 {
2312+
channel.node_two.as_slice()
2313+
} else {
2314+
channel.node_one.as_slice()
2315+
};
2316+
node_pubkey = PublicKey::from_slice(node_id)
2317+
.map_err(|_| LightningError{
23352318
err: "Couldn't parse source node pubkey".to_owned(),
23362319
action: ErrorAction::IgnoreAndLog(Level::Debug)
2337-
})?, "channel_update");
2338-
}
2339-
if !only_verify {
2340-
channel.two_to_one = get_new_channel_info!();
2341-
}
2342-
} else {
2343-
if let Some(sig) = sig {
2344-
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &PublicKey::from_slice(channel.node_one.as_slice()).map_err(|_| LightningError{
2345-
err: "Couldn't parse destination node pubkey".to_owned(),
2346-
action: ErrorAction::IgnoreAndLog(Level::Debug)
2347-
})?, "channel_update");
2348-
}
2349-
if !only_verify {
2350-
channel.one_to_two = get_new_channel_info!();
2351-
}
2352-
}
2320+
})?;
2321+
},
2322+
}
2323+
}
2324+
2325+
let msg_hash = hash_to_message!(&message_sha256d_hash(&msg)[..]);
2326+
if let Some(sig) = sig {
2327+
secp_verify_sig!(self.secp_ctx, &msg_hash, &sig, &node_pubkey, "channel_update");
2328+
}
2329+
2330+
if only_verify { return Ok(()); }
2331+
2332+
let mut channels = self.channels.write().unwrap();
2333+
if let Some(channel) = channels.get_mut(&msg.short_channel_id) {
2334+
check_msg_sanity(channel)?;
2335+
2336+
let last_update_message = if msg.excess_data.len() <= MAX_EXCESS_BYTES_FOR_RELAY
2337+
{ full_msg.cloned() } else { None };
2338+
2339+
let new_channel_info = Some(ChannelUpdateInfo {
2340+
enabled: chan_enabled,
2341+
last_update: msg.timestamp,
2342+
cltv_expiry_delta: msg.cltv_expiry_delta,
2343+
htlc_minimum_msat: msg.htlc_minimum_msat,
2344+
htlc_maximum_msat: msg.htlc_maximum_msat,
2345+
fees: RoutingFees {
2346+
base_msat: msg.fee_base_msat,
2347+
proportional_millionths: msg.fee_proportional_millionths,
2348+
},
2349+
last_update_message
2350+
});
2351+
2352+
if msg.channel_flags & 1 == 1 {
2353+
channel.two_to_one = new_channel_info;
2354+
} else {
2355+
channel.one_to_two = new_channel_info;
23532356
}
23542357
}
23552358

0 commit comments

Comments
 (0)