Skip to content

Commit f43404b

Browse files
Add EIP 7636 support (#81)
Co-authored-by: Age Manning <[email protected]>
1 parent 497349d commit f43404b

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

src/builder.rs

+16
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@ impl<K: EnrKey> Builder<K> {
110110
self
111111
}
112112

113+
/// Adds a [EIP-7636](https://eips.ethereum.org/EIPS/eip-7636) `client` field to the `ENRBuilder`.
114+
pub fn client_info(
115+
&mut self,
116+
name: String,
117+
version: String,
118+
build: Option<String>,
119+
) -> &mut Self {
120+
if build.is_none() {
121+
self.add_value("client", &vec![name, version]);
122+
} else {
123+
self.add_value("client", &vec![name, version, build.unwrap()]);
124+
}
125+
126+
self
127+
}
128+
113129
/// Generates the rlp-encoded form of the ENR specified by the builder config.
114130
fn rlp_content(&self) -> BytesMut {
115131
let mut list = Vec::<u8>::with_capacity(MAX_ENR_SIZE);

src/lib.rs

+120
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,36 @@ impl<K: EnrKey> Enr<K> {
343343
None
344344
}
345345

346+
/// Returns [EIP-7636](https://eips.ethereum.org/EIPS/eip-7636) entry if it is defined.
347+
#[must_use]
348+
pub fn client_info(&self) -> Option<(String, String, Option<String>)> {
349+
if let Some(Ok(client_list)) = self.get_decodable::<Vec<Bytes>>("client") {
350+
match client_list.len() {
351+
2 => {
352+
let client_name = String::from_utf8_lossy(client_list[0].as_ref()).to_string();
353+
354+
let client_version =
355+
String::from_utf8_lossy(client_list[1].as_ref()).to_string();
356+
357+
return Some((client_name, client_version, None));
358+
}
359+
3 => {
360+
let client_name = String::from_utf8_lossy(client_list[0].as_ref()).to_string();
361+
362+
let client_version =
363+
String::from_utf8_lossy(client_list[1].as_ref()).to_string();
364+
365+
let client_additional =
366+
String::from_utf8_lossy(client_list[2].as_ref()).to_string();
367+
return Some((client_name, client_version, Some(client_additional)));
368+
}
369+
_ => {}
370+
}
371+
}
372+
373+
None
374+
}
375+
346376
/// The TCP port of ENR record if it is defined.
347377
#[must_use]
348378
pub fn tcp4(&self) -> Option<u16> {
@@ -620,6 +650,23 @@ impl<K: EnrKey> Enr<K> {
620650
Ok(None)
621651
}
622652

653+
/// Sets the [EIP-7636](https://eips.ethereum.org/EIPS/eip-7636) `client` field in the record.
654+
pub fn set_client_info(
655+
&mut self,
656+
name: String,
657+
version: String,
658+
build: Option<String>,
659+
key: &K,
660+
) -> Result<(), Error> {
661+
if build.is_none() {
662+
self.insert("client", &vec![name, version], key)?;
663+
} else {
664+
self.insert("client", &vec![name, version, build.unwrap()], key)?;
665+
}
666+
667+
Ok(())
668+
}
669+
623670
/// Sets the IP and UDP port in a single update with a single increment in sequence number.
624671
pub fn set_udp_socket(&mut self, socket: SocketAddr, key: &K) -> Result<(), Error> {
625672
self.set_socket(socket, key, false)
@@ -1969,6 +2016,79 @@ mod tests {
19692016
assert_eq!(record.seq(), 30);
19702017
}
19712018

2019+
#[test]
2020+
fn test_set_client_eip7636() {
2021+
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
2022+
let mut enr = Enr::empty(&key).unwrap();
2023+
2024+
enr.set_client_info(
2025+
"Test".to_string(),
2026+
"v1.0.0".to_string(),
2027+
Some("Test".to_string()),
2028+
&key,
2029+
)
2030+
.unwrap();
2031+
assert!(enr.verify());
2032+
2033+
enr.set_client_info("Test".to_string(), "v1.0.0".to_string(), None, &key)
2034+
.unwrap();
2035+
assert!(enr.verify());
2036+
}
2037+
2038+
#[test]
2039+
fn test_get_eip7636() {
2040+
let example_eip = "enr:-MO4QBn4OF-y-dqULg4WOIlc8gQAt-arldNFe0_YQ4HNX28jDtg41xjDyKfCXGfZaPN97I-MCfogeK91TyqmWTpb0_AChmNsaWVudNqKTmV0aGVybWluZIYxLjkuNTOHN2ZjYjU2N4JpZIJ2NIJpcIR_AAABg2lwNpAAAAAAAAAAAAAAAAAAAAABiXNlY3AyNTZrMaECn-TTdCwfZP4XgJyq8Lxoj-SgEoIFgDLVBEUqQk4HnAqDdWRwgiMshHVkcDaCIyw";
2041+
let enr = example_eip.parse::<DefaultEnr>().unwrap();
2042+
2043+
let info = enr.client_info().unwrap();
2044+
2045+
assert_eq!(info.0, "Nethermind");
2046+
assert_eq!(info.1, "1.9.53");
2047+
assert_eq!(info.2.unwrap(), "7fcb567");
2048+
2049+
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
2050+
let mut enr = Enr::empty(&key).unwrap();
2051+
2052+
enr.set_client_info("Test".to_string(), "v1.0.0".to_string(), None, &key)
2053+
.unwrap();
2054+
2055+
let info = enr.client_info().unwrap();
2056+
assert_eq!(info.0, "Test");
2057+
assert_eq!(info.1, "v1.0.0");
2058+
assert_eq!(info.2, None);
2059+
}
2060+
2061+
#[test]
2062+
fn test_builder_eip7636() {
2063+
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
2064+
let enr = Enr::builder()
2065+
.ip4(Ipv4Addr::new(127, 0, 0, 1))
2066+
.tcp4(30303)
2067+
.client_info(
2068+
"Test".to_string(),
2069+
"v1.0.0".to_string(),
2070+
Some("Test".to_string()),
2071+
)
2072+
.build(&key)
2073+
.unwrap();
2074+
2075+
let info = enr.client_info().unwrap();
2076+
assert_eq!(info.0, "Test");
2077+
assert_eq!(info.1, "v1.0.0");
2078+
assert_eq!(info.2.unwrap(), "Test");
2079+
2080+
let enr = Enr::builder()
2081+
.ip4(Ipv4Addr::new(127, 0, 0, 1))
2082+
.tcp4(30303)
2083+
.client_info("Test".to_string(), "v1.0.0".to_string(), None)
2084+
.build(&key)
2085+
.unwrap();
2086+
2087+
let info = enr.client_info().unwrap();
2088+
assert_eq!(info.0, "Test");
2089+
assert_eq!(info.1, "v1.0.0");
2090+
assert_eq!(info.2, None);
2091+
}
19722092
/// Tests a common ENR which uses RLP encoded values without the header
19732093
#[test]
19742094
fn test_common_rlp_convention() {

0 commit comments

Comments
 (0)