diff --git a/src/network/service/discovery.rs b/src/network/service/discovery.rs index d554083..17a1205 100644 --- a/src/network/service/discovery.rs +++ b/src/network/service/discovery.rs @@ -1,16 +1,17 @@ +//! Module handles Discv5 discovery + use std::{str::FromStr, time::Duration}; +use super::enr::OpStackEnrData; use discv5::{ enr::{CombinedKey, Enr, EnrBuilder, NodeId}, Discv5, Discv5Config, }; -use ethers::utils::rlp; use eyre::Result; use tokio::{ sync::mpsc::{self, Receiver}, time::sleep, }; -use unsigned_varint::{decode, encode}; use super::types::{NetworkAddress, Peer}; @@ -81,43 +82,6 @@ fn create_disc(chain_id: u64) -> Result { Discv5::new(enr, key, config).map_err(|_| eyre::eyre!("could not create disc service")) } -/// The unique L2 network identifier -#[derive(Debug)] -struct OpStackEnrData { - /// Chain ID - chain_id: u64, - /// The version. Always set to 0. - version: u64, -} - -impl TryFrom<&[u8]> for OpStackEnrData { - type Error = eyre::Report; - - /// Converts a slice of RLP encoded bytes to [OpStackEnrData] - fn try_from(value: &[u8]) -> Result { - let bytes: Vec = rlp::decode(value)?; - let (chain_id, rest) = decode::u64(&bytes)?; - let (version, _) = decode::u64(rest)?; - - Ok(Self { chain_id, version }) - } -} - -impl From for Vec { - /// Converts [OpStackEnrData] to a vector of bytes. - fn from(value: OpStackEnrData) -> Vec { - let mut chain_id_buf = encode::u128_buffer(); - let chain_id_slice = encode::u128(value.chain_id as u128, &mut chain_id_buf); - - let mut version_buf = encode::u128_buffer(); - let version_slice = encode::u128(value.version as u128, &mut version_buf); - - let opstack = [chain_id_slice, version_slice].concat(); - - rlp::encode(&opstack).to_vec() - } -} - /// Default bootnodes to use. Currently consists of 2 Base bootnodes & 1 Optimism bootnode. fn bootnodes() -> Vec> { let bootnodes = [ diff --git a/src/network/service/enr.rs b/src/network/service/enr.rs new file mode 100644 index 0000000..a10aeda --- /dev/null +++ b/src/network/service/enr.rs @@ -0,0 +1,93 @@ +//! Contains the [OpStackEnrData] struct. + +use alloy_rlp::{Buf, Decodable, Encodable, Error}; +use eyre::Result; + +#[derive(Debug, Copy, Default, PartialEq, Clone)] +pub struct OpStackEnrData { + /// Chain ID + pub chain_id: u64, + /// The version. Always set to 0. + pub version: u64, +} + +impl Decodable for OpStackEnrData { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + buf.advance(1); // Advance past the string rlp type + let (chain_id, rest) = + unsigned_varint::decode::u64(buf).map_err(|_| Error::Custom("Invalid chain id"))?; + let (version, _) = + unsigned_varint::decode::u64(rest).map_err(|_| Error::Custom("Invalid version"))?; + Ok(OpStackEnrData { chain_id, version }) + } +} + +impl Encodable for OpStackEnrData { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { + out.put_u8(0x87); // RLP string type + let encoded: &mut [u8; 10] = &mut [0; 10]; + let chain_id = unsigned_varint::encode::u64(self.chain_id, encoded); + out.put_slice(chain_id); + let version = unsigned_varint::encode::u64(self.version, encoded); + out.put_slice(version); + } +} + +impl TryFrom<&[u8]> for OpStackEnrData { + type Error = eyre::Report; + + /// Converts a slice of RLP encoded bytes to [OpStackEnrData] + fn try_from(mut value: &[u8]) -> Result { + Ok(OpStackEnrData::decode(&mut value)?) + } +} + +impl From for Vec { + /// Converts [OpStackEnrData] to a vector of bytes. + fn from(value: OpStackEnrData) -> Vec { + let mut bytes = Vec::new(); + value.encode(&mut bytes); + bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_rlp::BytesMut; + + #[test] + fn test_decode_encode_raw() { + let raw = &[0x87, 0x7b, 0x01]; + let decoded = OpStackEnrData::try_from(&raw[..]).unwrap(); + assert_eq!(decoded.chain_id, 123); + assert_eq!(decoded.version, 1); + let mut buf = BytesMut::new(); + decoded.encode(&mut buf); + assert_eq!(&buf[..], raw); + } + + #[test] + fn test_empty_round_trip() { + let data = OpStackEnrData { + chain_id: 0, + version: 0, + }; + let bytes: Vec = data.into(); + let decoded = OpStackEnrData::try_from(bytes.as_slice()).unwrap(); + assert_eq!(decoded.chain_id, 0); + assert_eq!(decoded.version, 0); + } + + #[test] + fn test_round_trip_large() { + let data = OpStackEnrData { + chain_id: u64::MAX / 2, + version: u64::MAX / 3, + }; + let bytes: Vec = data.into(); + let decoded = OpStackEnrData::try_from(bytes.as_slice()).unwrap(); + assert_eq!(decoded.chain_id, data.chain_id); + assert_eq!(decoded.version, data.version); + } +} diff --git a/src/network/service/mod.rs b/src/network/service/mod.rs index f40fb39..6c4b6b3 100644 --- a/src/network/service/mod.rs +++ b/src/network/service/mod.rs @@ -14,9 +14,8 @@ use openssl::sha::sha256; use super::{handlers::Handler, service::types::NetworkAddress}; -/// A module to handle peer discovery mod discovery; -/// A module to handle commonly used types in the p2p system. +mod enr; mod types; /// Responsible for management of the `Discv5` & `libp2p` services. diff --git a/src/network/service/types.rs b/src/network/service/types.rs index 03dc0f5..d8f8cde 100644 --- a/src/network/service/types.rs +++ b/src/network/service/types.rs @@ -1,3 +1,4 @@ +/// Module contains commonly used types in the p2p system. use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use discv5::enr::{CombinedKey, Enr};