Skip to content

Conversation

@lakshya-sky
Copy link
Contributor

@lakshya-sky lakshya-sky commented Dec 6, 2025

Motivation
Instead of storing vec of arbitary blob use NodeRecordPairs which allow updating ENRs less tedious.

  • Every time adding/removing a key-value would require sorting the vec which is not optimal.
  • Checking a certain key-value requires decoding whole key-value list is also less optimal.

Description

Closes #5068

@lakshya-sky lakshya-sky marked this pull request as ready for review December 6, 2025 17:31
@lakshya-sky lakshya-sky requested a review from a team as a code owner December 6, 2025 17:31
Copilot AI review requested due to automatic review settings December 11, 2025 19:58
Copy link
Contributor

Copilot AI left a 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 refactors the NodeRecord structure to use a structured NodeRecordPairs type instead of a raw Vec<(Bytes, Bytes)> for storing ENR (Ethereum Node Record) entries. This change simplifies ENR manipulation by eliminating the need for manual sorting and encoding/decoding of key-value pairs.

Key Changes

  • Moved NodeRecordPairs struct before NodeRecord and made the pairs field in NodeRecord private with typed access
  • Implemented encode_pairs() and refactored decode_pairs() as static methods on NodeRecordPairs to handle conversion between structured and raw formats
  • Added helper methods get_fork_id() and update() to NodeRecord for cleaner fork ID management and record updates

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
crates/networking/p2p/types.rs Restructured NodeRecord to use NodeRecordPairs internally; added encode/decode methods; added snap field support; refactored from_node and set_fork_id methods
crates/networking/p2p/discv4/server.rs Updated to use new get_fork_id() method instead of decode_pairs()
crates/networking/p2p/discv4/messages.rs Updated test constructors to use new NodeRecord::new() and decode_pairs() static method
crates/common/types/fork_id.rs Added Serialize and Deserialize derives to ForkId

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 423 to 424
pub fn get_fork_id(&self) -> Option<ForkId> {
self.pairs.eth.clone()
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary clone of fork_id. Since ForkId is being moved into self.pairs.eth, you can directly move it without cloning: self.pairs.eth = Some(fork_id); (which you already do correctly). However, in get_fork_id, consider returning a reference instead of cloning the entire ForkId if possible.

Suggested change
pub fn get_fork_id(&self) -> Option<ForkId> {
self.pairs.eth.clone()
pub fn get_fork_id(&self) -> Option<&ForkId> {
self.pairs.eth.as_ref()

Copilot uses AI. Check for mistakes.
if bytes.len() < 33 {
continue;
return Err(RLPDecodeError::Custom(format!(
"Invalid signature length {}",
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message refers to "signature" but this validation is for the secp256k1 public key field, not a signature. Update the error message to be more accurate, e.g., "Invalid secp256k1 public key length: expected at least 33 bytes, got {}".

Suggested change
"Invalid signature length {}",
"Invalid secp256k1 public key length: expected at least 33 bytes, got {}",

Copilot uses AI. Check for mistakes.
impl NodeRecord {
pub fn decode_pairs(&self) -> NodeRecordPairs {
impl NodeRecordPairs {
pub fn decode_pairs(pairs: &Vec<(Bytes, Bytes)>) -> Result<NodeRecordPairs, RLPDecodeError> {
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method signature is confusing - this is a static method that takes a Vec reference but is called decode_pairs. Consider renaming to from_pairs or try_from_pairs to better reflect that this is a constructor-like method that creates a new NodeRecordPairs instance from raw key-value pairs.

Copilot uses AI. Check for mistakes.
Comment on lines 293 to 308
"id" => decoded_pairs.id = Some(String::decode(&value)?),
"ip" => decoded_pairs.ip = Some(Ipv4Addr::decode(&value)?),
"ip6" => decoded_pairs.ip6 = Some(Ipv6Addr::decode(&value)?),
"tcp" => decoded_pairs.tcp_port = Some(u16::decode(&value)?),
"udp" => decoded_pairs.udp_port = Some(u16::decode(&value)?),
"secp256k1" => {
let Ok(bytes) = Bytes::decode(&value) else {
continue;
};
let bytes = Bytes::decode(&value)?;
if bytes.len() < 33 {
continue;
return Err(RLPDecodeError::Custom(format!(
"Invalid signature length {}",
bytes.len()
)));
}
decoded_pairs.secp256k1 = Some(H264::from_slice(&bytes))
}
"snap" => decoded_pairs.snap = Some(Vec::<u32>::decode(&value)?),
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling has changed from silently skipping decode failures to returning errors. For known keys like "id", "ip", "tcp", etc., if decoding fails with ?, the entire decode operation now fails instead of continuing. This is a breaking behavior change that could reject previously accepted ENR records with malformed values. Verify this stricter validation is intentional, or consider maintaining backward compatibility by continuing to skip malformed known fields.

Copilot uses AI. Check for mistakes.
pub signature: H512,
pub seq: u64,
/// The remainder of the record consists of arbitrary key/value pairs
pairs: NodeRecordPairs,
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pairs field is now private but there's no getter method provided. However, a pairs() method is added later that returns a clone. Consider if this should be a direct reference getter instead, or if making the field public would be more appropriate for better API design, especially since the old structure had this field public.

Copilot uses AI. Check for mistakes.
Comment on lines 453 to 454
pub fn pairs(&self) -> NodeRecordPairs {
self.pairs.clone()
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pairs() method returns a clone of the entire NodeRecordPairs structure, which could be inefficient if called frequently. Consider whether a reference would be more appropriate here, or document why cloning is necessary.

Suggested change
pub fn pairs(&self) -> NodeRecordPairs {
self.pairs.clone()
pub fn pairs(&self) -> &NodeRecordPairs {
&self.pairs

Copilot uses AI. Check for mistakes.
node_record: NodeRecord,
) -> Result<(), DiscoveryServerError> {
let pairs = node_record.decode_pairs();
let local_fork_id = node_record.get_fork_id();
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name local_fork_id is misleading - this is actually the fork ID from the received node record, not the local fork ID. Consider renaming to node_fork_id or received_fork_id for clarity.

Copilot uses AI. Check for mistakes.
pub struct NodeRecord {
pub signature: H512,
pub seq: u64,
/// The remainder of the record consists of arbitrary key/value pairs
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "The remainder of the record consists of arbitrary key/value pairs" but the field is now of type NodeRecordPairs, which is a specific structured type, not arbitrary pairs. Update the comment to reflect the actual implementation, such as "The remainder of the record consists of key/value pairs represented as NodeRecordPairs".

Suggested change
/// The remainder of the record consists of arbitrary key/value pairs
/// The remainder of the record consists of key/value pairs represented as NodeRecordPairs

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Simplify ENR handling

1 participant