Skip to content

Commit

Permalink
feat(net): Networking Crate (#34)
Browse files Browse the repository at this point in the history
### Description

Sets up a `net` crate based off of
paradigmxyz/reth-exex-examples#15.

### Metadata

Makes progress on #6
  • Loading branch information
refcell authored Aug 26, 2024
1 parent 6f9d2a7 commit 410d078
Show file tree
Hide file tree
Showing 22 changed files with 2,904 additions and 176 deletions.
1,678 changes: 1,502 additions & 176 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ categories = ["cryptography", "cryptography::cryptocurrencies"]
members = [
"bin/hera",
"bin/op-rs",
"crates/net",
"crates/kona-providers",
"crates/rollup",
"crates/ser",
Expand Down Expand Up @@ -115,6 +116,9 @@ codegen-units = 1
incremental = false

[workspace.dependencies]
# Workspace
op-net = { path = "crates/net" }

# Optimism
superchain-registry = { version = "0.2.6", default-features = false }
kona-primitives = { git = "https://github.com/ethereum-optimism/kona", default-features = true }
Expand All @@ -133,6 +137,9 @@ alloy-rlp = "0.3.4"
# Tokio
tokio = { version = "1.21", default-features = false }

# SSZ
ssz_rs = "0.9.0"

# Reth
reth = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" }
reth-chainspec = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" }
Expand All @@ -149,12 +156,22 @@ reth-revm = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" }
reth-evm = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" }
reth-tracing = { git = "https://github.com/paradigmxyz/reth", version = "1.0.5" }

# Networking
snap = "1.1.1"
discv5 = "0.6.0"
openssl = { version = "0.10.66", features = ["vendored"] }
libp2p-identity = { version = "0.2.9", features = [ "secp256k1" ] }
libp2p = { version = "0.54.0", features = ["macros", "tokio", "tcp", "noise", "gossipsub", "ping", "yamux"] }

# Misc
tracing = "0.1.0"
eyre = "0.6.12"
clap = "4"
lazy_static = "1.5.0"
futures = "0.3.30"
async-trait = "0.1.81"
hashbrown = "0.14.5"
parking_lot = "0.12.3"
unsigned-varint = "0.8.0"
rand = { version = "0.8.3", features = ["small_rng"], default-features = false }
url = "2.5.2"
35 changes: 35 additions & 0 deletions crates/net/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "op-net"
description = "Networking library for OP Stack Consensus"
version = "0.0.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
keywords.workspace = true
repository.workspace = true
categories.workspace = true
rust-version.workspace = true

[dependencies]
# Alloy
alloy.workspace = true
alloy-rlp.workspace = true

# Kona
kona-primitives.workspace = true

# Networking
ssz_rs.workspace = true
snap.workspace = true
futures.workspace = true
discv5.workspace = true
libp2p.workspace = true
openssl.workspace = true
libp2p-identity.workspace = true

# Misc
eyre.workspace = true
tokio.workspace = true
tracing.workspace = true
lazy_static.workspace = true
unsigned-varint.workspace = true
7 changes: 7 additions & 0 deletions crates/net/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Network Library

Contains a gossipsub driver to run discv5 peer discovery and block gossip.

## Acknowledgements

Largely based off [magi][https://github.com/a16z/magi]'s [p2p module](https://github.com/a16z/magi/tree/master/src/network).
139 changes: 139 additions & 0 deletions crates/net/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//! Network Builder Module.
use alloy::primitives::Address;
use eyre::Result;
use std::net::SocketAddr;
use tokio::sync::watch::channel;

use libp2p::{
gossipsub::ConfigBuilder, noise::Config as NoiseConfig, tcp::Config as TcpConfig,
yamux::Config as YamuxConfig, Multiaddr, SwarmBuilder,
};
use libp2p_identity::Keypair;

use crate::{
discovery::builder::DiscoveryBuilder,
driver::NetworkDriver,
gossip::{behaviour::Behaviour, config, driver::GossipDriver, handler::BlockHandler},
types::address::NetworkAddress,
};

/// Constructs a [NetworkDriver] for Optimism's consensus-layer.
#[derive(Default)]
pub struct NetworkDriverBuilder {
/// The chain ID of the network.
chain_id: Option<u64>,
/// The unsafe block signer.
unsafe_block_signer: Option<Address>,
/// The socket address that the service is listening on.
socket: Option<SocketAddr>,
/// The [ConfigBuilder] constructs the config for `gossipsub`.
inner: Option<ConfigBuilder>,
/// The [Keypair] for the node.
keypair: Option<Keypair>,
/// The [TcpConfig] for the swarm.
tcp_config: Option<TcpConfig>,
// /// The [NoiseConfig] for the swarm.
// noise_config: Option<NoiseConfig>,
/// The [YamuxConfig] for the swarm.
yamux_config: Option<YamuxConfig>,
}

impl NetworkDriverBuilder {
/// Creates a new [NetworkDriverBuilder].
pub fn new() -> Self {
Self::default()
}

/// Specifies the chain ID of the network.
pub fn with_chain_id(&mut self, chain_id: u64) -> &mut Self {
self.chain_id = Some(chain_id);
self
}

/// Specifies the unsafe block signer.
pub fn with_unsafe_block_signer(&mut self, unsafe_block_signer: Address) -> &mut Self {
self.unsafe_block_signer = Some(unsafe_block_signer);
self
}

/// Specifies the socket address that the service is listening on.
pub fn with_socket(&mut self, socket: SocketAddr) -> &mut Self {
self.socket = Some(socket);
self
}

/// Specifies the keypair for the node.
pub fn with_keypair(&mut self, keypair: Keypair) -> &mut Self {
self.keypair = Some(keypair);
self
}

/// Specifies the [TcpConfig] for the swarm.
pub fn with_tcp_config(&mut self, tcp_config: TcpConfig) -> &mut Self {
self.tcp_config = Some(tcp_config);
self
}

// /// Specifies the [NoiseConfig] for the swarm.
// pub fn with_noise_config(&mut self, noise_config: NoiseConfig) -> &mut Self {
// self.noise_config = Some(noise_config);
// self
// }

/// Specifies the [YamuxConfig] for the swarm.
pub fn with_yamux_config(&mut self, yamux_config: YamuxConfig) -> &mut Self {
self.yamux_config = Some(yamux_config);
self
}

// TODO: extend builder with [ConfigBuilder] methods.

/// Specifies the [ConfigBuilder] for the `gossipsub` configuration.
pub fn with_gossip_config(&mut self, inner: ConfigBuilder) -> &mut Self {
self.inner = Some(inner);
self
}

/// Builds the [NetworkDriver].
pub fn build(self) -> Result<NetworkDriver> {
// Build the config for gossipsub.
let config = self.inner.unwrap_or(config::default_config_builder()).build()?;
let unsafe_block_signer =
self.unsafe_block_signer.ok_or_else(|| eyre::eyre!("unsafe block signer not set"))?;
let chain_id = self.chain_id.ok_or_else(|| eyre::eyre!("chain ID not set"))?;

// Create the block handler.
let (unsafe_block_signer_sender, unsafe_block_signer_recv) = channel(unsafe_block_signer);
let (handler, unsafe_block_recv) = BlockHandler::new(chain_id, unsafe_block_signer_recv);

// Construct the gossipsub behaviour.
let behaviour = Behaviour::new(config, &[Box::new(handler.clone())])?;

// Build the swarm.
let keypair = self.keypair.unwrap_or(Keypair::generate_secp256k1());
let swarm = SwarmBuilder::with_existing_identity(keypair)
.with_tokio()
.with_tcp(self.tcp_config.unwrap_or_default(), NoiseConfig::new, || {
self.yamux_config.unwrap_or_default()
})?
.with_behaviour(|_| behaviour)?
.build();
let addr = self.socket.ok_or_else(|| eyre::eyre!("socket address not set"))?;
let addr = NetworkAddress::try_from(addr)?;
let swarm_addr = Multiaddr::from(addr);
let swarm = GossipDriver::new(swarm, swarm_addr);

// Build the discovery service
let discovery =
DiscoveryBuilder::new().with_address(addr).with_chain_id(chain_id).build()?;

Ok(NetworkDriver {
unsafe_block_recv,
unsafe_block_signer_sender,
handler,
swarm,
discovery,
})
}
}
22 changes: 22 additions & 0 deletions crates/net/src/discovery/bootnodes.rs
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();
}
59 changes: 59 additions & 0 deletions crates/net/src/discovery/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//! Contains a builder for the discovery service.
use crate::{
discovery::driver::DiscoveryDriver,
types::{address::NetworkAddress, enr::OpStackEnr},
};
use discv5::{
enr::{CombinedKey, Enr},
ConfigBuilder, Discv5, ListenConfig,
};
use eyre::Result;

use crate::types::enr::OP_CL_KEY;

/// 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
}

/// Builds a [DiscoveryDriver].
pub fn build(self) -> Result<DiscoveryDriver> {
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(OP_CL_KEY, opstack_data.into()).build(&key)?;
let listen_config = ListenConfig::from_ip(addr.ip.into(), addr.port);
let config = ConfigBuilder::new(listen_config).build();

let disc = Discv5::new(enr, key, config)
.map_err(|_| eyre::eyre!("could not create disc service"))?;

Ok(DiscoveryDriver::new(disc, chain_id))
}
}
Loading

0 comments on commit 410d078

Please sign in to comment.