-
Notifications
You must be signed in to change notification settings - Fork 131
feat(l1): detect and update stale enr on ping pong #5507
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
feat(l1): detect and update stale enr on ping pong #5507
Conversation
| Ok(()) | ||
| } | ||
|
|
||
| async fn send_enr_request(&mut self, node: &Node) -> Result<(), DiscoveryServerError> { |
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.
Let's factorize common code (message encoding, hash extraction, udp_socket.send, maybe set_disposable) with send_ping/send_ping_internal to an utility function to minimize code repetiton.
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.
Added send_else_dispose to remove code duplication.
| if let (Some(received), Some(stored)) = (received_enr_seq, stored_enr_seq) | ||
| && received > stored | ||
| { | ||
| self.send_enr_request(&node).await?; | ||
| } |
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 let (Some(received), Some(stored)) = (received_enr_seq, stored_enr_seq) | |
| && received > stored | |
| { | |
| self.send_enr_request(&node).await?; | |
| } | |
| if received_enr_seq.is_some_and(|r| stored_enr_seq.is_some_and(|s| r > s)) { | |
| self.send_enr_request(&node).await?; | |
| } |
Not sure which one I prefer 🤔
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.
Maybe transpose:
| if let (Some(received), Some(stored)) = (received_enr_seq, stored_enr_seq) | |
| && received > stored | |
| { | |
| self.send_enr_request(&node).await?; | |
| } | |
| if [received_enr_seq, stored_enr_seq].transpose().is_some_and(|[received, stored]| received > stored) { | |
| self.send_enr_request(&node).await?; | |
| } |
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.
Or:
| if let (Some(received), Some(stored)) = (received_enr_seq, stored_enr_seq) | |
| && received > stored | |
| { | |
| self.send_enr_request(&node).await?; | |
| } | |
| if let Some([received, stored]) = [received_enr_seq, stored_enr_seq].transpose() && received > stored { | |
| self.send_enr_request(&node).await?; | |
| } |
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.
transpose is an unstable feature.
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.
😭
MegaRedHand
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.
LGTM
| .is_err() | ||
| { | ||
| self.peer_table | ||
| .set_disposable(&node.node_id()) | ||
| .await?; | ||
| METRICS.record_new_discarded_node().await; | ||
| } |
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.
Either put this inside the inspect_err or convert everything to an if let Err(e)...
Having two ways to check the same thing in the same place makes the code more confusing than it needs to be.
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.
changed to if let Err(e).. for better error handling.
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
This PR implements automatic detection and updating of stale Ethereum Node Records (ENR) during the ping/pong protocol exchange in the Discovery v4 implementation. When nodes exchange ping/pong messages that include ENR sequence numbers, the code now compares them against stored values and automatically requests updated ENRs when a peer advertises a newer version.
Key Changes:
- Adds ENR sequence number comparison logic in both ping and pong message handlers to detect when peers have newer ENRs
- Refactors message sending code by extracting reusable helper methods (
send_enr_requestandsend_else_dispose) - Adds a new
get_contactmethod to the peer table for retrieving contacts by node_id
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
crates/networking/p2p/discv4/server.rs |
Implements ENR staleness detection in ping/pong handlers, refactors message sending into helper methods, and updates the lookup methods for cleaner error handling |
crates/networking/p2p/discv4/peer_table.rs |
Adds get_contact method and corresponding message handling to support retrieving contacts by node_id |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self.send_ping(&node).await?; | ||
| } else { | ||
| // If the contact has stale ENR then request the updated one. | ||
| let node_id = node_id(&sender_public_key); |
Copilot
AI
Dec 11, 2025
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.
The node_id is being computed redundantly here. The node parameter already has the same node_id since it was created from sender_public_key (see line 159-164). You can use node.node_id() directly instead of recomputing node_id(&sender_public_key) to avoid the extra keccak hash computation.
| let node_id = node_id(&sender_public_key); | |
| let node_id = node.node_id(); |
| } else { | ||
| // If the contact has stale ENR then request the updated one. | ||
| let node_id = node_id(&sender_public_key); | ||
| let stored_enr_seq = self | ||
| .peer_table | ||
| .get_contact(node_id) | ||
| .await? | ||
| .and_then(|c| c.record) | ||
| .map(|r| r.seq); | ||
|
|
||
| let received_enr_seq = ping_message.enr_seq; | ||
|
|
||
| if let (Some(received), Some(stored)) = (received_enr_seq, stored_enr_seq) | ||
| && received > stored | ||
| { | ||
| self.send_enr_request(&node).await?; | ||
| } |
Copilot
AI
Dec 11, 2025
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.
The new ENR staleness detection logic in handle_ping lacks test coverage. Consider adding tests to verify that ENR requests are sent when a ping is received with a higher sequence number than the stored ENR.
| // If the contact has stale ENR then request the updated one. | ||
| let stored_enr_seq = contact.record.map(|r| r.seq); | ||
| let received_enr_seq = message.enr_seq; | ||
| if let (Some(received), Some(stored)) = (received_enr_seq, stored_enr_seq) | ||
| && received > stored | ||
| { | ||
| self.send_enr_request(&contact.node).await?; | ||
| } |
Copilot
AI
Dec 11, 2025
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.
The new ENR staleness detection logic in handle_pong lacks test coverage. Consider adding tests to verify that ENR requests are sent when a pong is received with a higher sequence number than the stored ENR.
| async fn send_else_dispose( | ||
| &mut self, | ||
| message: Message, | ||
| node: &Node, | ||
| ) -> Result<H256, DiscoveryServerError> { | ||
| let mut buf = BytesMut::new(); | ||
| message.encode_with_header(&mut buf, &self.signer); | ||
| let message_hash: [u8; 32] = buf[..32] | ||
| .try_into() | ||
| .expect("first 32 bytes are the message hash"); | ||
| if let Err(e) = self.udp_socket.send_to(&buf, node.udp_addr()).await { | ||
| error!(sending = ?message, addr = ?node.udp_addr(), to = ?node.node_id(), err=?e, "Error sending message"); | ||
| self.peer_table.set_disposable(&node.node_id()).await?; | ||
| METRICS.record_new_discarded_node().await; | ||
| } | ||
| Ok(H256::from(message_hash)) | ||
| } |
Copilot
AI
Dec 11, 2025
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.
The new send_else_dispose helper method lacks test coverage. Consider adding tests to verify its behavior when sending succeeds and when it fails, particularly ensuring that the node is marked as disposable and metrics are recorded on failure.
ElFantasma
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.
Nice!
Motivation
Description
Closes #5488