Skip to content

Commit

Permalink
Merge latest master
Browse files Browse the repository at this point in the history
  • Loading branch information
AgeManning committed Oct 22, 2024
2 parents fad7fbf + 80d220e commit 11429e0
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 27 deletions.
25 changes: 24 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,25 @@
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# This project does not check in Cargo.lock
Cargo.lock
target

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# Generated by Intellij-based IDEs.
.idea

# Generated by MacOS
.DS_Store

# VSCode
.vscode

# Rust bug report
rustc-ice-*
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "enr"
authors = ["Age Manning <[email protected]>"]
edition = "2021"
version = "0.12.0"
version = "0.12.1"
description = "Rust implementation of Ethereum Node Record (ENR) EIP778"
readme = "./README.md"
keywords = ["ethereum", "enr", "record", "EIP778", "node"]
Expand All @@ -23,13 +23,13 @@ sha3 = "0.10"
k256 = { version = "0.13", features = ["ecdsa"], optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
ed25519-dalek = { version = "2.1.1", optional = true, features = ["rand_core"] }
secp256k1 = { version = "0.28", optional = true, default-features = false, features = [
secp256k1 = { version = "0.29", optional = true, default-features = false, features = [
"global-context",
] }

[dev-dependencies]
alloy-rlp = { version = "0.3.4", features = ["derive"] }
secp256k1 = { version = "0.28", features = ["rand-std"] }
secp256k1 = { version = "0.29", features = ["rand-std"] }
serde_json = "1.0"

[features]
Expand Down
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ let mut enr = Enr::builder().ip4(ip).tcp4(8000).build(&key).unwrap();

enr.set_tcp4(8001, &key);
// set a custom key
enr.insert("custom_key", &vec![0,0,1], &key);
enr.insert("custom_key", &[0,0,1], &key);

// encode to base64
let base_64_string = enr.to_base64();
Expand All @@ -128,39 +128,37 @@ let decoded_enr: DefaultEnr = base_64_string.parse().unwrap();
assert_eq!(decoded_enr.ip4(), Some("192.168.0.1".parse().unwrap()));
assert_eq!(decoded_enr.id(), Some("v4".into()));
assert_eq!(decoded_enr.tcp4(), Some(8001));
assert_eq!(decoded_enr.get("custom_key"), Some(vec![0,0,1].as_slice()));
assert_eq!(decoded_enr.get("custom_key").as_ref().map(AsRef::as_ref), Some(vec![0,0,1]).as_deref());
```

#### Encoding/Decoding ENR's of various key types

```rust
use enr::{k256::ecdsa::SigningKey, Enr, ed25519_dalek::Keypair, CombinedKey};
use enr::{ed25519_dalek as ed25519, k256::ecdsa, CombinedKey, Enr};
use std::net::Ipv4Addr;
use rand::thread_rng;
use rand::Rng;

// generate a random secp256k1 key
let mut rng = thread_rng();
let key = SigningKey::random(&mut rng);
let ip = Ipv4Addr::new(192,168,0,1);
let key = ecdsa::SigningKey::random(&mut rng);
let ip = Ipv4Addr::new(192, 168, 0, 1);
let enr_secp256k1 = Enr::builder().ip4(ip).tcp4(8000).build(&key).unwrap();

// encode to base64
let base64_string_secp256k1 = enr_secp256k1.to_base64();

// generate a random ed25519 key
let mut rng = rand_07::thread_rng();
let key = Keypair::generate(&mut rng);
let key = ed25519::SigningKey::generate(&mut rng);
let enr_ed25519 = Enr::builder().ip4(ip).tcp4(8000).build(&key).unwrap();

// encode to base64
let base64_string_ed25519 = enr_ed25519.to_base64();

// decode base64 strings of varying key types
// decode the secp256k1 with default Enr
let decoded_enr_secp256k1: Enr<k256::ecdsa::SigningKey> = base64_string_secp256k1.parse().unwrap();
let decoded_enr_secp256k1: Enr<ecdsa::SigningKey> = base64_string_secp256k1.parse().unwrap();
// decode ed25519 ENRs
let decoded_enr_ed25519: Enr<ed25519_dalek::Keypair> = base64_string_ed25519.parse().unwrap();
let decoded_enr_ed25519: Enr<ed25519::SigningKey> = base64_string_ed25519.parse().unwrap();

// use the combined key to be able to decode either
let decoded_enr: Enr<CombinedKey> = base64_string_secp256k1.parse().unwrap();
Expand Down
22 changes: 19 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<K: EnrKey> Builder<K> {

/// Adds an arbitrary key-value to the `ENRBuilder`.
pub fn add_value<T: Encodable>(&mut self, key: impl AsRef<[u8]>, value: &T) -> &mut Self {
let mut out = BytesMut::new();
let mut out = BytesMut::with_capacity(value.length());
value.encode(&mut out);
self.add_value_rlp(key, out.freeze())
}
Expand Down Expand Up @@ -114,6 +114,22 @@ impl<K: EnrKey> Builder<K> {
self
}

/// Adds a [EIP-7636](https://eips.ethereum.org/EIPS/eip-7636) `client` field to the `ENRBuilder`.
pub fn client_info(
&mut self,
name: String,
version: String,
build: Option<String>,
) -> &mut Self {
if build.is_none() {
self.add_value("client", &vec![name, version]);
} else {
self.add_value("client", &vec![name, version, build.unwrap()]);
}

self
}

/// Generates the rlp-encoded form of the ENR specified by the builder config.
fn rlp_content(&self) -> BytesMut {
let mut list = Vec::<u8>::with_capacity(MAX_ENR_SIZE);
Expand All @@ -128,7 +144,7 @@ impl<K: EnrKey> Builder<K> {
list: true,
payload_length: list.len(),
};
let mut out = BytesMut::new();
let mut out = BytesMut::with_capacity(header.length() + list.len());
header.encode(&mut out);
out.extend_from_slice(&list);
out
Expand Down Expand Up @@ -165,7 +181,7 @@ impl<K: EnrKey> Builder<K> {
Header::decode(&mut value.as_ref())?;
}

let mut id_bytes = BytesMut::with_capacity(3);
let mut id_bytes = BytesMut::with_capacity(self.id.length());
self.id.as_bytes().encode(&mut id_bytes);
self.add_value_rlp(ID_ENR_KEY, id_bytes.freeze());

Expand Down
159 changes: 149 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,9 @@
//!
//! ```rust
//! # #[cfg(feature = "ed25519")] {
//! use enr::{k256::ecdsa, Enr, ed25519_dalek as ed25519, CombinedKey};
//! use enr::{ed25519_dalek as ed25519, k256::ecdsa, CombinedKey, Enr};
//! use std::net::Ipv4Addr;
//! use rand::thread_rng;
//! use rand::Rng;
//!
//! // generate a random secp256k1 key
//! let mut rng = thread_rng();
Expand All @@ -155,9 +154,9 @@
//!
//! // decode base64 strings of varying key types
//! // decode the secp256k1 with default Enr
//! let decoded_enr_secp256k1: Enr<k256::ecdsa::SigningKey> = base64_string_secp256k1.parse().unwrap();
//! let decoded_enr_secp256k1: Enr<ecdsa::SigningKey> = base64_string_secp256k1.parse().unwrap();
//! // decode ed25519 ENRs
//! let decoded_enr_ed25519: Enr<ed25519_dalek::SigningKey> = base64_string_ed25519.parse().unwrap();
//! let decoded_enr_ed25519: Enr<ed25519::SigningKey> = base64_string_ed25519.parse().unwrap();
//!
//! // use the combined key to be able to decode either
//! let decoded_enr: Enr<CombinedKey> = base64_string_secp256k1.parse().unwrap();
Expand Down Expand Up @@ -352,6 +351,36 @@ impl<K: EnrKey> Enr<K> {
None
}

/// Returns [EIP-7636](https://eips.ethereum.org/EIPS/eip-7636) entry if it is defined.
#[must_use]
pub fn client_info(&self) -> Option<(String, String, Option<String>)> {
if let Some(Ok(client_list)) = self.get_decodable::<Vec<Bytes>>("client") {
match client_list.len() {
2 => {
let client_name = String::from_utf8_lossy(client_list[0].as_ref()).to_string();

let client_version =
String::from_utf8_lossy(client_list[1].as_ref()).to_string();

return Some((client_name, client_version, None));
}
3 => {
let client_name = String::from_utf8_lossy(client_list[0].as_ref()).to_string();

let client_version =
String::from_utf8_lossy(client_list[1].as_ref()).to_string();

let client_additional =
String::from_utf8_lossy(client_list[2].as_ref()).to_string();
return Some((client_name, client_version, Some(client_additional)));
}
_ => {}
}
}

None
}

/// The TCP port of ENR record if it is defined.
#[must_use]
pub fn tcp4(&self) -> Option<u16> {
Expand Down Expand Up @@ -649,6 +678,23 @@ impl<K: EnrKey> Enr<K> {
self.remove_key(TCP6_ENR_KEY, key)
}

/// Sets the [EIP-7636](https://eips.ethereum.org/EIPS/eip-7636) `client` field in the record.
pub fn set_client_info(
&mut self,
name: String,
version: String,
build: Option<String>,
key: &K,
) -> Result<(), Error> {
if build.is_none() {
self.insert("client", &vec![name, version], key)?;
} else {
self.insert("client", &vec![name, version, build.unwrap()], key)?;
}

Ok(())
}

/// Sets the IP and UDP port in a single update with a single increment in sequence number.
pub fn set_udp_socket(&mut self, socket: SocketAddr, key: &K) -> Result<(), Error> {
self.set_socket(socket, key, false)
Expand Down Expand Up @@ -872,7 +918,7 @@ impl<K: EnrKey> Enr<K> {

/// Sets a new public key for the record.
pub fn set_public_key(&mut self, public_key: &K::PublicKey, key: &K) -> Result<(), Error> {
self.insert(&public_key.enr_key(), &public_key.encode().as_ref(), key)
self.insert(public_key.enr_key(), &public_key.encode().as_ref(), key)
.map(|_| {})
}

Expand Down Expand Up @@ -1134,12 +1180,19 @@ impl<K: EnrKey> Decodable for Enr<K> {
_ => {
let other_header = Header::decode(payload)?;
let value = &payload[..other_header.payload_length];
// Preserve the valid encoding
payload.advance(other_header.payload_length);
let mut out = Vec::<u8>::new();
other_header.encode(&mut out);
out.extend_from_slice(value);
out

// Encode the header for list values, for non-list objects, we remove the
// header for compatibility with commonly used key entries (i.e it's the
// current convention).
if other_header.list {
let mut out = Vec::<u8>::new();
other_header.encode(&mut out);
out.extend_from_slice(value);
out
} else {
alloy_rlp::encode(value)
}
}
};
content.insert(key.to_vec(), Bytes::from(value));
Expand Down Expand Up @@ -2058,4 +2111,90 @@ mod tests {
record.set_seq(30, &key).unwrap();
assert_eq!(record.seq(), 30);
}

#[test]
fn test_set_client_eip7636() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let mut enr = Enr::empty(&key).unwrap();

enr.set_client_info(
"Test".to_string(),
"v1.0.0".to_string(),
Some("Test".to_string()),
&key,
)
.unwrap();
assert!(enr.verify());

enr.set_client_info("Test".to_string(), "v1.0.0".to_string(), None, &key)
.unwrap();
assert!(enr.verify());
}

#[test]
fn test_get_eip7636() {
let example_eip = "enr:-MO4QBn4OF-y-dqULg4WOIlc8gQAt-arldNFe0_YQ4HNX28jDtg41xjDyKfCXGfZaPN97I-MCfogeK91TyqmWTpb0_AChmNsaWVudNqKTmV0aGVybWluZIYxLjkuNTOHN2ZjYjU2N4JpZIJ2NIJpcIR_AAABg2lwNpAAAAAAAAAAAAAAAAAAAAABiXNlY3AyNTZrMaECn-TTdCwfZP4XgJyq8Lxoj-SgEoIFgDLVBEUqQk4HnAqDdWRwgiMshHVkcDaCIyw";
let enr = example_eip.parse::<DefaultEnr>().unwrap();

let info = enr.client_info().unwrap();

assert_eq!(info.0, "Nethermind");
assert_eq!(info.1, "1.9.53");
assert_eq!(info.2.unwrap(), "7fcb567");

let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let mut enr = Enr::empty(&key).unwrap();

enr.set_client_info("Test".to_string(), "v1.0.0".to_string(), None, &key)
.unwrap();

let info = enr.client_info().unwrap();
assert_eq!(info.0, "Test");
assert_eq!(info.1, "v1.0.0");
assert_eq!(info.2, None);
}

#[test]
fn test_builder_eip7636() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let enr = Enr::builder()
.ip4(Ipv4Addr::new(127, 0, 0, 1))
.tcp4(30303)
.client_info(
"Test".to_string(),
"v1.0.0".to_string(),
Some("Test".to_string()),
)
.build(&key)
.unwrap();

let info = enr.client_info().unwrap();
assert_eq!(info.0, "Test");
assert_eq!(info.1, "v1.0.0");
assert_eq!(info.2.unwrap(), "Test");

let enr = Enr::builder()
.ip4(Ipv4Addr::new(127, 0, 0, 1))
.tcp4(30303)
.client_info("Test".to_string(), "v1.0.0".to_string(), None)
.build(&key)
.unwrap();

let info = enr.client_info().unwrap();
assert_eq!(info.0, "Test");
assert_eq!(info.1, "v1.0.0");
assert_eq!(info.2, None);
}
/// Tests a common ENR which uses RLP encoded values without the header
#[test]
fn test_common_rlp_convention() {
const COMMON_VALID_ENR: &str = concat!(
"-LW4QCAyOCtqvQjd8AgpqbaCgfjy8oN8cBBRT5jtzarkGJQWZx1eN70EM0QafVCugLa-Bv493DPNzflagqfTOsWSF78Ih2F0d",
"G5ldHOIAGAAAAAAAACEZXRoMpBqlaGpBAAAAP__________gmlkgnY0hHF1aWOCIymJc2VjcDI1NmsxoQPg_HgqXzwRIK39Oy",
"lGdC30YUFwsfXvATnGUvEZ6MtBQIhzeW5jbmV0cwCDdGNwgiMo"
);

// Expect this to be able to be decoded
let _decoded: DefaultEnr = COMMON_VALID_ENR.parse().unwrap();
}
}

0 comments on commit 11429e0

Please sign in to comment.