-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
230 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
//! Bootnodes for consensus network discovery. | ||
use discv5::enr::{CombinedKey, Enr}; | ||
use lazy_static::lazy_static; | ||
use std::str::FromStr; | ||
|
||
lazy_static! { | ||
/// Default bootnodes to use. | ||
pub static ref BOOTNODES: Vec<Enr<CombinedKey>> = [ | ||
// Optimism Mainnet Bootnodes | ||
Enr::from_str("enr:-J64QBbwPjPLZ6IOOToOLsSjtFUjjzN66qmBZdUexpO32Klrc458Q24kbty2PdRaLacHM5z-cZQr8mjeQu3pik6jPSOGAYYFIqBfgmlkgnY0gmlwhDaRWFWHb3BzdGFja4SzlAUAiXNlY3AyNTZrMaECmeSnJh7zjKrDSPoNMGXoopeDF4hhpj5I0OsQUUt4u8uDdGNwgiQGg3VkcIIkBg").unwrap(), | ||
Enr::from_str("enr:-J64QAlTCDa188Hl1OGv5_2Kj2nWCsvxMVc_rEnLtw7RPFbOfqUOV6khXT_PH6cC603I2ynY31rSQ8sI9gLeJbfFGaWGAYYFIrpdgmlkgnY0gmlwhANWgzCHb3BzdGFja4SzlAUAiXNlY3AyNTZrMaECkySjcg-2v0uWAsFsZZu43qNHppGr2D5F913Qqs5jDCGDdGNwgiQGg3VkcIIkBg").unwrap(), | ||
Enr::from_str("enr:-J24QGEzN4mJgLWNTUNwj7riVJ2ZjRLenOFccl2dbRFxHHOCCZx8SXWzgf-sLzrGs6QgqSFCvGXVgGPBkRkfOWlT1-iGAYe6Cu93gmlkgnY0gmlwhCJBEUSHb3BzdGFja4OkAwCJc2VjcDI1NmsxoQLuYIwaYOHg3CUQhCkS-RsSHmUd1b_x93-9yQ5ItS6udIN0Y3CCIyuDdWRwgiMr").unwrap(), | ||
|
||
// Base Mainnet Bootnodes | ||
Enr::from_str("enr:-J24QNz9lbrKbN4iSmmjtnr7SjUMk4zB7f1krHZcTZx-JRKZd0kA2gjufUROD6T3sOWDVDnFJRvqBBo62zuF-hYCohOGAYiOoEyEgmlkgnY0gmlwhAPniryHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQKNVFlCxh_B-716tTs-h1vMzZkSs1FTu_OYTNjgufplG4N0Y3CCJAaDdWRwgiQG").unwrap(), | ||
Enr::from_str("enr:-J24QH-f1wt99sfpHy4c0QJM-NfmsIfmlLAMMcgZCUEgKG_BBYFc6FwYgaMJMQN5dsRBJApIok0jFn-9CS842lGpLmqGAYiOoDRAgmlkgnY0gmlwhLhIgb2Hb3BzdGFja4OFQgCJc2VjcDI1NmsxoQJ9FTIv8B9myn1MWaC_2lJ-sMoeCDkusCsk4BYHjjCq04N0Y3CCJAaDdWRwgiQG").unwrap(), | ||
Enr::from_str("enr:-J24QDXyyxvQYsd0yfsN0cRr1lZ1N11zGTplMNlW4xNEc7LkPXh0NAJ9iSOVdRO95GPYAIc6xmyoCCG6_0JxdL3a0zaGAYiOoAjFgmlkgnY0gmlwhAPckbGHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQJwoS7tzwxqXSyFL7g0JM-KWVbgvjfB8JA__T7yY_cYboN0Y3CCJAaDdWRwgiQG").unwrap(), | ||
Enr::from_str("enr:-J24QHmGyBwUZXIcsGYMaUqGGSl4CFdx9Tozu-vQCn5bHIQbR7On7dZbU61vYvfrJr30t0iahSqhc64J46MnUO2JvQaGAYiOoCKKgmlkgnY0gmlwhAPnCzSHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQINc4fSijfbNIiGhcgvwjsjxVFJHUstK9L1T8OTKUjgloN0Y3CCJAaDdWRwgiQG").unwrap(), | ||
Enr::from_str("enr:-J24QG3ypT4xSu0gjb5PABCmVxZqBjVw9ca7pvsI8jl4KATYAnxBmfkaIuEqy9sKvDHKuNCsy57WwK9wTt2aQgcaDDyGAYiOoGAXgmlkgnY0gmlwhDbGmZaHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQIeAK_--tcLEiu7HvoUlbV52MspE0uCocsx1f_rYvRenIN0Y3CCJAaDdWRwgiQG").unwrap(), | ||
].to_vec(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
//! Discovery Module. | ||
use eyre::Result; | ||
use std::time::Duration; | ||
use tokio::{ | ||
sync::mpsc::{channel, Receiver}, | ||
time::sleep, | ||
}; | ||
use tracing::{trace, warn}; | ||
|
||
use discv5::{ | ||
enr::{CombinedKey, Enr, NodeId}, | ||
ConfigBuilder, Discv5, ListenConfig, | ||
}; | ||
|
||
use crate::{ | ||
bootnodes::BOOTNODES, | ||
op_enr::OpStackEnr, | ||
types::{NetworkAddress, Peer}, | ||
}; | ||
|
||
/// The number of peers to buffer in the channel. | ||
const DISCOVERY_PEER_CHANNEL_SIZE: usize = 256; | ||
|
||
/// Discovery service builder. | ||
#[derive(Debug, Default, Clone)] | ||
pub struct DiscoveryBuilder { | ||
/// The discovery service address. | ||
address: Option<NetworkAddress>, | ||
/// The chain ID of the network. | ||
chain_id: Option<u64>, | ||
} | ||
|
||
impl DiscoveryBuilder { | ||
/// Creates a new discovery builder. | ||
pub fn new() -> Self { | ||
Self::default() | ||
} | ||
|
||
/// Sets the discovery service address. | ||
pub fn with_address(mut self, address: NetworkAddress) -> Self { | ||
self.address = Some(address); | ||
self | ||
} | ||
|
||
/// Sets the chain ID of the network. | ||
pub fn with_chain_id(mut self, chain_id: u64) -> Self { | ||
self.chain_id = Some(chain_id); | ||
self | ||
} | ||
|
||
/// Generates an [Enr] and creates a [Discv5] service struct | ||
fn create_disc(&self) -> Result<Discv5> { | ||
let addr = self.address.ok_or_else(|| eyre::eyre!("address not set"))?; | ||
let chain_id = self.chain_id.ok_or_else(|| eyre::eyre!("chain ID not set"))?; | ||
let opstack = OpStackEnr::new(chain_id, 0); | ||
let opstack_data: Vec<u8> = opstack.into(); | ||
|
||
let key = CombinedKey::generate_secp256k1(); | ||
let enr = Enr::builder().add_value_rlp("opstack", opstack_data.into()).build(&key)?; | ||
let listen_config = ListenConfig::from_ip(addr.ip.into(), addr.port); | ||
let config = ConfigBuilder::new(listen_config).build(); | ||
|
||
Discv5::new(enr, key, config).map_err(|_| eyre::eyre!("could not create disc service")) | ||
} | ||
|
||
/// Spawns a new [Discv5] discovery service in a new tokio task. | ||
/// | ||
/// Returns a [Receiver] to receive [Peer] structs. | ||
/// | ||
/// ## Errors | ||
/// | ||
/// Returns an error if the address or chain ID is not set | ||
/// on the [DiscoveryBuilder]. | ||
/// | ||
/// ## Example | ||
/// | ||
/// ```no_run | ||
/// use op_net::discovery::DiscoveryBuilder; | ||
/// | ||
/// let builder = DiscoveryBuilder::new() | ||
/// .with_address("") | ||
/// .with_chain_id(10) // OP Mainnet chain id | ||
/// .start() | ||
/// .expect("Failed to start discovery service"); | ||
/// | ||
/// loop { | ||
/// if let Some(peer) = builder.recv().await { | ||
/// println!("Received peer: {:?}", peer); | ||
/// } | ||
/// ``` | ||
pub fn start(self) -> Result<Receiver<Peer>> { | ||
let chain_id = self.chain_id.ok_or_else(|| eyre::eyre!("chain ID not set"))?; | ||
|
||
// Clone the bootnodes since the spawned thread takes mutable ownership. | ||
let bootnodes = BOOTNODES.clone(); | ||
|
||
// Construct the discovery service. | ||
let mut disc = self.create_disc()?; | ||
|
||
// Create a multi-producer, single-consumer (mpsc) channel to receive | ||
// peers bounded by `DISCOVERY_PEER_CHANNEL_SIZE`. | ||
let (sender, recv) = channel::<Peer>(DISCOVERY_PEER_CHANNEL_SIZE); | ||
|
||
tokio::spawn(async move { | ||
bootnodes.into_iter().for_each(|enr| _ = disc.add_enr(enr)); | ||
disc.start().await.unwrap(); | ||
|
||
trace!("Started peer discovery"); | ||
|
||
loop { | ||
let target = NodeId::random(); | ||
match disc.find_node(target).await { | ||
Ok(nodes) => { | ||
let peers = nodes | ||
.iter() | ||
.filter(|node| OpStackEnr::is_valid_node(node, chain_id)) | ||
.flat_map(Peer::try_from); | ||
|
||
for peer in peers { | ||
_ = sender.send(peer).await; | ||
} | ||
} | ||
Err(err) => { | ||
warn!("discovery error: {:?}", err); | ||
} | ||
} | ||
|
||
sleep(Duration::from_secs(10)).await; | ||
} | ||
}); | ||
|
||
Ok(recv) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
//! Contains the OP Stack Enr Type. | ||
use discv5::enr::{CombinedKey, Enr}; | ||
use eyre::Result; | ||
use unsigned_varint::{decode, encode}; | ||
|
||
/// The unique L2 network identifier | ||
#[derive(Debug, Clone, Copy, Default)] | ||
pub struct OpStackEnr { | ||
/// Chain ID | ||
pub chain_id: u64, | ||
/// The version. Always set to 0. | ||
pub version: u64, | ||
} | ||
|
||
impl OpStackEnr { | ||
/// Instantiates a new Op Stack Enr. | ||
pub fn new(chain_id: u64, version: u64) -> Self { | ||
Self { chain_id, version } | ||
} | ||
|
||
/// Returns `true` if a node [Enr] contains an `opstack` key and is on the same network. | ||
pub fn is_valid_node(node: &Enr<CombinedKey>, chain_id: u64) -> bool { | ||
node.get_raw_rlp("opstack") | ||
.map(|opstack| { | ||
OpStackEnr::try_from(opstack) | ||
.map(|opstack| opstack.chain_id == chain_id && opstack.version == 0) | ||
.unwrap_or_default() | ||
}) | ||
.unwrap_or_default() | ||
} | ||
} | ||
|
||
impl TryFrom<&[u8]> for OpStackEnr { | ||
type Error = eyre::Report; | ||
|
||
/// Converts a slice of RLP encoded bytes to Op Stack Enr Data. | ||
fn try_from(value: &[u8]) -> Result<Self> { | ||
// TODO: rlp decode first? | ||
// let bytes = Vec::<u8>::decode(&mut value)?; | ||
let mut bytes = value; | ||
let (chain_id, rest) = | ||
decode::u64(bytes).map_err(|_| eyre::eyre!("could not decode chain id"))?; | ||
bytes = rest; | ||
let (version, _) = | ||
decode::u64(bytes).map_err(|_| eyre::eyre!("could not decode chain id"))?; | ||
|
||
Ok(Self { chain_id, version }) | ||
} | ||
} | ||
|
||
impl From<OpStackEnr> for Vec<u8> { | ||
/// Converts Op Stack Enr data to a vector of bytes. | ||
fn from(value: OpStackEnr) -> Vec<u8> { | ||
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(); | ||
|
||
alloy_rlp::encode(&opstack).to_vec() | ||
} | ||
} |