-
Notifications
You must be signed in to change notification settings - Fork 27
feat(networking): update ports in ENR #457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
dknopik
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
|
Some required checks have failed. Could you please take a look @petarjuki7? 🙏 |
| ) { | ||
| } | ||
|
|
||
| #[allow(clippy::single_match)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather keep it a single match, so it shows there are more variants we are ignoring for now.
I can change it to a if let Some(event)... if needed, no problem.
anchor/network/src/discovery.rs
Outdated
| } | ||
| } | ||
|
|
||
| /// Updates the local ENR TCP port. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Internal helper that updates a single ENR port field (IPv4 or IPv6) if it differs.
///
/// - `desired_port` is the port we want to have in the ENR.
/// - `is_ipv6` selects the v6 accessor/key when `true`, otherwise v4.
/// - `key_v4` / `key_v6` are the ENR key names to write (e.g. `"tcp"` / `"tcp6"`).
/// - `current_v4` / `current_v6` are accessors that read the current port
/// from the external ENR (`tcp4()`, `tcp6()`, `quic4()`, `quic6()`, etc.).
///
/// Returns:
/// - `Ok(true)` — field updated and ENR persisted to disk.
/// - `Ok(false)` — no change required (already set to `desired_port`).
/// - `Err(_)` — failed to write into the ENR.
fn update_enr_port<Fv4, Fv6>(
&mut self,
desired_port: u16,
is_ipv6: bool,
key_v4: &'static str,
key_v6: &'static str,
current_v4: Fv4,
current_v6: Fv6,
) -> Result<bool, String>
where
Fv4: Fn(&Enr<CombinedKey>) -> Option<u16>,
Fv6: Fn(&Enr<CombinedKey>) -> Option<u16>,
{
// Check if the value is already set.
{
let external_enr = self.discv5.external_enr().read();
let already_set = if is_ipv6 {
current_v6(&external_enr) == Some(desired_port)
} else {
current_v4(&external_enr) == Some(desired_port)
};
if already_set {
return Ok(false);
}
}
// Update the appropriate ENR key.
let enr_key = if is_ipv6 { key_v6 } else { key_v4 };
self.discv5
.enr_insert(enr_key, &desired_port)
.map_err(|e| format!("{e:?}"))?;
// Persist modified ENR to disk.
save_enr_to_disk(Path::new(&self.enr_dir), &self.discv5.local_enr());
Ok(true)
}
/// Update the ENR **TCP** port (IPv4 or IPv6).
///
/// This only updates the port field in the ENR and **does not** modify the address.
/// Discovery is expected to update the external address automatically.
/// If you need to change the external address, use `update_enr_udp_socket`.
///
/// Returns `Ok(true)` if the ENR was changed and persisted, `Ok(false)` if the
/// existing value already matches `port`.
pub fn update_enr_tcp_port(&mut self, port: u16, is_ipv6: bool) -> Result<bool, String> {
self.update_enr_port(port, is_ipv6, "tcp", "tcp6", |e| e.tcp4(), |e| e.tcp6())
}
/// Update the ENR **QUIC** port (IPv4 or IPv6).
///
/// This only updates the port field in the ENR and **does not** modify the address.
/// Discovery is expected to update the external address automatically.
/// If you need to change the external address, use `update_enr_udp_socket`.
///
/// Returns `Ok(true)` if the ENR was changed and persisted, `Ok(false)` if the
/// existing value already matches `port`.
pub fn update_enr_quic_port(&mut self, port: u16, is_ipv6: bool) -> Result<bool, String> {
self.update_enr_port(port, is_ipv6, "quic", "quic6", |e| e.quic4(), |e| e.quic6())
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdyt about keeping the if is_ipv6 in update_enr_tcp_port and update_enr_quic_port? Then we only have the parameters desired_port, key and current to the main function, which seems easier to understand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we would keep some duplication. IMO, the current code isn't that hard to understand, but I don't have a strong opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I combined the two aproaches, the closures were elegant but maybe a bit hard to reason about at first glance IMO, can change it either way
anchor/network/src/discovery.rs
Outdated
| if (self.update_ports.tcp4 && socket_addr.is_ipv4()) | ||
| || (self.update_ports.tcp6 && socket_addr.is_ipv6()) | ||
| { | ||
| self.discv5.update_local_enr_socket(socket_addr, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have to call update_local_enr_socket if SocketUpdated is described as /// Our local ENR IP address has been updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm reading the discv5 code correctly, the SocketUpdated event is emitted when only the UDP port is updated, with this we update our TCP port.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks. The event and comment in discv5 should be changed to make it clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I agree
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that after this event, the IP address and the UDP port have been updated. self.discv5.update_local_enr_socket(socket_addr, true) updates the IP address and the TCP port, but it doesn't update the QUIC one. Would it be sufficient to update only the TCP and QUIC ports instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afaik the discv5 doesn't "speak" QUIC, that's why we dont update it. We update it when we get the NewListenAddr event from libp2p because we can directly check if the QUIC port changed. We can maybe assume and update it here also, but this SocketUpdated event doesn't tell us about QUIC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discv5 doesn't know anything about the TCP port either
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True... This is the way it was done in Lighthouse so I took it as inspiration, should I also do update_enr_quic_port() here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking more about it, we can't set the discv5 UDP and QUIC port to the same value. I believe LH sets the TCP port here as a way to improve connectivity for nodes behind a NAT. But does nothing for QUIC. @AgeManning could you please clarify?
|
What do you think about using only one function to update the port? /// Try to update an ENR port based on port type and configuration.
///
/// This method centralizes all port update logic in one place:
/// 1. Checks if updates are allowed for this port type
/// 2. Gets current port value from ENR
/// 3. Updates the port if needed
/// 4. Persists changes to disk
///
/// Parameters:
/// - `is_tcp`: Whether this is a TCP port (true) or QUIC port (false)
/// - `is_ipv6`: Whether this is an IPv6 port (true) or IPv4 port (false)
/// - `port`: The new port value to set
///
/// Returns:
/// - `Ok(true)`: Port was updated and persisted to disk
/// - `Ok(false)`: No update was needed (config disallows it or port already matches)
/// - `Err(String)`: Update failed with the given error message
pub fn try_update_port(&mut self, is_tcp: bool, is_ipv6: bool, new_port: u16) -> Result<bool, String> {
let (read_fn, key): (fn(&_) -> Option<u16>, &str) = match (is_tcp, is_ipv6) {
(true, false) if self.update_ports.tcp4 => (Enr::tcp4, "tcp"),
(true, true) if self.update_ports.tcp6 => (Enr::tcp6, "tcp6"),
(false, false) if self.update_ports.quic4 => (Enr::quic4, "quic4"),
(false, true) if self.update_ports.quic6 => (Enr::quic6, "quic6"),
_ => return Ok(false)
};
let port_opt = read_fn(&self.discv5.external_enr().read());
if port_opt == Some(new_port) {
return Ok(false);
}
self.discv5
.enr_insert(key, &new_port)
.map_err(|e| format!("{e:?}"))?;
save_enr_to_disk(Path::new(&self.enr_dir), &self.discv5.local_enr());
Ok(true)
} |
|
Hi @petarjuki7 , are you still interested in completing this, or can I go ahead and continue? |
Hi, if it's fine you can continue. Thanks @diegomrsantos |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Updates the networking layer to dynamically update ENR (Ethereum Node Record) ports based on actual listening addresses from both libp2p and discv5 events, addressing issue #255.
- Listen to libp2p's
NewListenAddrevents to update ENR with TCP and QUIC ports - Listen to discv5's
SocketUpdatedevents to update ENR with UDP discovery ports - Implement centralized port update logic with proper validation and disk persistence
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| anchor/network/src/network.rs | Adds handling of NewListenAddr events and implements port update logic for TCP/QUIC protocols |
| anchor/network/src/discovery.rs | Implements try_update_port method and handles discv5 SocketUpdated events for UDP port updates |
# Conflicts: # anchor/network/src/discovery.rs # anchor/network/src/network.rs
dknopik
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found a bug while improving the code and decide to just push it instead of describing it:
| if let EventStream::Awaiting(future) = self | ||
| && let Poll::Ready(Ok(receiver)) = future.as_mut().poll(cx) | ||
| { | ||
| *self = EventStream::Present(receiver); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this as we else never actually have a present EventStream
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we construct Discovery, we put an EventStream::Awaiting into event_stream. We need to convert it into an EventStream::Present somewhere.
diegomrsantos
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @petarjuki7 for starting this.
Addresses issue sigp#255 Much of the logic insipred by Lighthouse. Listening on discv5's `SocketUpdated` event and updating the local ENR socket. Listening on libp2p's `NewListenAddr` and updating the ENR accordingly.
Addresses issue sigp#255 Much of the logic insipred by Lighthouse. Listening on discv5's `SocketUpdated` event and updating the local ENR socket. Listening on libp2p's `NewListenAddr` and updating the ENR accordingly.
Issue Addressed
Addresses issue #255
Proposed Changes
Much of the logic insipred by Lighthouse.
Listening on discv5's
SocketUpdatedevent and updating the local ENR socket.Listening on libp2p's
NewListenAddrand updating the ENR accordingly.Additional Info
Needed to finish #444