diff --git a/src/builder.rs b/src/builder.rs index 123dcd5..7306ea0 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -110,6 +110,22 @@ impl Builder { 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, + ) -> &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::::with_capacity(MAX_ENR_SIZE); diff --git a/src/lib.rs b/src/lib.rs index 3c7e369..faeb4ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -343,6 +343,36 @@ impl Enr { 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)> { + if let Some(Ok(client_list)) = self.get_decodable::>("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 { @@ -620,6 +650,23 @@ impl Enr { Ok(None) } + /// 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, + 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) @@ -1969,6 +2016,79 @@ mod tests { 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::().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() {