diff --git a/beacon_node/client/src/config.rs b/beacon_node/client/src/config.rs new file mode 100644 index 000000000..415ef0ec9 --- /dev/null +++ b/beacon_node/client/src/config.rs @@ -0,0 +1,67 @@ +use clap::ArgMatches; +use http_server::HttpServerConfig; +use network::NetworkConfig; +use serde_derive::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; + +/// The core configuration of a Lighthouse beacon node. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Config { + pub data_dir: PathBuf, + pub db_type: String, + db_name: String, + pub network: network::NetworkConfig, + pub rpc: rpc::RPCConfig, + pub http: HttpServerConfig, +} + +impl Default for Config { + fn default() -> Self { + Self { + data_dir: PathBuf::from(".lighthouse"), + db_type: "disk".to_string(), + db_name: "chain_db".to_string(), + // Note: there are no default bootnodes specified. + // Once bootnodes are established, add them here. + network: NetworkConfig::new(), + rpc: rpc::RPCConfig::default(), + http: HttpServerConfig::default(), + } + } +} + +impl Config { + /// Returns the path to which the client may initialize an on-disk database. + pub fn db_path(&self) -> Option { + self.data_dir() + .and_then(|path| Some(path.join(&self.db_name))) + } + + /// Returns the core path for the client. + pub fn data_dir(&self) -> Option { + let path = dirs::home_dir()?.join(&self.data_dir); + fs::create_dir_all(&path).ok()?; + Some(path) + } + + /// Apply the following arguments to `self`, replacing values if they are specified in `args`. + /// + /// Returns an error if arguments are obviously invalid. May succeed even if some values are + /// invalid. + pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { + if let Some(dir) = args.value_of("datadir") { + self.data_dir = PathBuf::from(dir); + }; + + if let Some(dir) = args.value_of("db") { + self.db_type = dir.to_string(); + } + + self.network.apply_cli_args(args)?; + self.rpc.apply_cli_args(args)?; + self.http.apply_cli_args(args)?; + + Ok(()) + } +} diff --git a/beacon_node/eth2-libp2p/Cargo.toml b/beacon_node/eth2-libp2p/Cargo.toml index fd7162767..d66127df9 100644 --- a/beacon_node/eth2-libp2p/Cargo.toml +++ b/beacon_node/eth2-libp2p/Cargo.toml @@ -8,8 +8,8 @@ edition = "2018" beacon_chain = { path = "../beacon_chain" } clap = "2.32.0" # SigP repository -libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "f018f5c443ed5a93de890048dbc6755393373e72" } -enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "f018f5c443ed5a93de890048dbc6755393373e72", features = ["serde"] } + libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "1c2aa97a338fc9dfd8e804cc47497fb9b8e7ad04" } +enr = { git = "https://github.com/SigP/rust-libp2p/", rev = "1c2aa97a338fc9dfd8e804cc47497fb9b8e7ad04", features = ["serde"] } types = { path = "../../eth2/types" } serde = "1.0" serde_derive = "1.0" diff --git a/beacon_node/eth2-libp2p/src/config.rs b/beacon_node/eth2-libp2p/src/config.rs index 00a8ed51e..cf9422520 100644 --- a/beacon_node/eth2-libp2p/src/config.rs +++ b/beacon_node/eth2-libp2p/src/config.rs @@ -1,9 +1,6 @@ use clap::ArgMatches; use enr::Enr; -use libp2p::{ - gossipsub::{GossipsubConfig, GossipsubConfigBuilder}, - multiaddr::Multiaddr, -}; +use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder}; use serde_derive::{Deserialize, Serialize}; use std::time::Duration; @@ -18,9 +15,12 @@ pub const SHARD_TOPIC_PREFIX: &str = "shard"; /// Network configuration for lighthouse. pub struct Config { /// IP address to listen on. - pub listen_addresses: Vec, + pub listen_address: std::net::IpAddr, - /// Specifies the IP address that the discovery protocol will listen on. + /// The TCP port that libp2p listens on. + pub libp2p_port: u16, + + /// The address to broadcast to peers about which address we are listening on. pub discovery_address: std::net::IpAddr, /// UDP port that discovery listens on. @@ -47,8 +47,9 @@ impl Default for Config { /// Generate a default network configuration. fn default() -> Self { Config { - listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000".parse().expect("vaild multiaddr")], - discovery_address: "0.0.0.0".parse().expect("valid ip address"), + listen_address: "127.0.0.1".parse().expect("vaild ip address"), + libp2p_port: 9000, + discovery_address: "127.0.0.1".parse().expect("valid ip address"), discovery_port: 9000, max_peers: 10, //TODO: Set realistic values for production @@ -72,13 +73,11 @@ impl Config { pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), String> { if let Some(listen_address_str) = args.value_of("listen-address") { - self.listen_addresses = listen_address_str - .split(',') - .map(|a| { - a.parse::() - .map_err(|_| format!("Invalid Listen address: {:?}", a)) - }) - .collect::, _>>()?; + let listen_address = listen_address_str + .parse() + .map_err(|_| format!("Invalid listen address: {:?}", listen_address_str))?; + self.listen_address = listen_address; + self.discovery_address = listen_address; } if let Some(max_peers_str) = args.value_of("maxpeers") { @@ -87,10 +86,12 @@ impl Config { .map_err(|_| format!("Invalid number of max peers: {}", max_peers_str))?; } - if let Some(discovery_address_str) = args.value_of("disc-listen-address") { - self.discovery_address = discovery_address_str - .parse::() - .map_err(|_| format!("Invalid discovery address: {:?}", discovery_address_str))?; + if let Some(port_str) = args.value_of("port") { + let port = port_str + .parse::() + .map_err(|_| format!("Invalid port: {}", port_str))?; + self.libp2p_port = port; + self.discovery_port = port; } if let Some(boot_enr_str) = args.value_of("boot-nodes") { @@ -100,6 +101,12 @@ impl Config { .collect::, _>>()?; } + if let Some(discovery_address_str) = args.value_of("discovery-address") { + self.discovery_address = discovery_address_str + .parse() + .map_err(|_| format!("Invalid discovery address: {:?}", discovery_address_str))? + } + if let Some(disc_port_str) = args.value_of("disc-port") { self.discovery_port = disc_port_str .parse::() diff --git a/beacon_node/eth2-libp2p/src/discovery.rs b/beacon_node/eth2-libp2p/src/discovery.rs index 9a1e75691..1d4563552 100644 --- a/beacon_node/eth2-libp2p/src/discovery.rs +++ b/beacon_node/eth2-libp2p/src/discovery.rs @@ -11,7 +11,7 @@ use libp2p::core::{identity::Keypair, Multiaddr, PeerId, ProtocolsHandler}; use libp2p::discv5::{Discv5, Discv5Event}; use libp2p::enr::{Enr, EnrBuilder, NodeId}; use libp2p::multiaddr::Protocol; -use slog::{debug, error, info, o, warn}; +use slog::{debug, info, o, warn}; use std::collections::HashSet; use std::time::{Duration, Instant}; use tokio::io::{AsyncRead, AsyncWrite}; @@ -19,6 +19,8 @@ use tokio_timer::Delay; /// Maximum seconds before searching for extra peers. const MAX_TIME_BETWEEN_PEER_SEARCHES: u64 = 60; +/// Initial delay between peer searches. +const INITIAL_SEARCH_DELAY: u64 = 5; /// Lighthouse discovery behaviour. This provides peer management and discovery using the Discv5 /// libp2p protocol. @@ -57,50 +59,27 @@ impl Discovery { let log = log.new(o!("Service" => "Libp2p-Discovery")); // Build the local ENR. - // The first TCP listening address is used for the ENR record. This will inform our peers to - // connect to this TCP port and establish libp2p streams. // Note: Discovery should update the ENR record's IP to the external IP as seen by the // majority of our peers. - let tcp_multiaddr = net_conf - .listen_addresses - .iter() - .filter(|a| { - if let Some(Protocol::Tcp(_)) = a.iter().last() { - true - } else { - false - } - }) - .next() - .ok_or_else(|| "No valid TCP addresses")?; - - let ip: std::net::IpAddr = match tcp_multiaddr.iter().next() { - Some(Protocol::Ip4(ip)) => ip.into(), - Some(Protocol::Ip6(ip)) => ip.into(), - _ => { - error!(log, "Multiaddr has an invalid IP address"); - return Err(format!("Invalid IP Address: {}", tcp_multiaddr).into()); - } - }; - - let tcp_port = match tcp_multiaddr.iter().last() { - Some(Protocol::Tcp(tcp)) => tcp, - _ => unreachable!(), - }; let local_enr = EnrBuilder::new() - .ip(ip.into()) - .tcp(tcp_port) + .ip(net_conf.discovery_address.into()) + .tcp(net_conf.libp2p_port) .udp(net_conf.discovery_port) .build(&local_key) .map_err(|e| format!("Could not build Local ENR: {:?}", e))?; info!(log, "Local ENR: {}", local_enr.to_base64()); - let mut discovery = Discv5::new(local_enr, local_key.clone(), net_conf.discovery_address) + let mut discovery = Discv5::new(local_enr, local_key.clone(), net_conf.listen_address) .map_err(|e| format!("Discv5 service failed: {:?}", e))?; // Add bootnodes to routing table for bootnode_enr in net_conf.boot_nodes.clone() { + debug!( + log, + "Adding node to routing table: {}", + bootnode_enr.node_id() + ); discovery.add_enr(bootnode_enr); } @@ -108,8 +87,8 @@ impl Discovery { connected_peers: HashSet::new(), max_peers: net_conf.max_peers, peer_discovery_delay: Delay::new(Instant::now()), - past_discovery_delay: 1, - tcp_port, + past_discovery_delay: INITIAL_SEARCH_DELAY, + tcp_port: net_conf.libp2p_port, discovery, log, }) @@ -118,7 +97,7 @@ impl Discovery { /// Manually search for peers. This restarts the discovery round, sparking multiple rapid /// queries. pub fn discover_peers(&mut self) { - self.past_discovery_delay = 1; + self.past_discovery_delay = INITIAL_SEARCH_DELAY; self.find_peers(); } @@ -203,7 +182,9 @@ where loop { match self.peer_discovery_delay.poll() { Ok(Async::Ready(_)) => { - self.find_peers(); + if self.connected_peers.len() < self.max_peers { + self.find_peers(); + } } Ok(Async::NotReady) => break, Err(e) => { @@ -217,16 +198,9 @@ where match self.discovery.poll(params) { Async::Ready(NetworkBehaviourAction::GenerateEvent(event)) => { match event { - Discv5Event::Discovered(enr) => { - debug!(self.log, "Discv5: Peer discovered"; "Peer"=> format!("{:?}", enr.peer_id()), "Addresses" => format!("{:?}", enr.multiaddr())); - - let peer_id = enr.peer_id(); - // if we need more peers, attempt a connection - if self.connected_peers.len() < self.max_peers - && self.connected_peers.get(&peer_id).is_none() - { - return Async::Ready(NetworkBehaviourAction::DialPeer { peer_id }); - } + Discv5Event::Discovered(_enr) => { + // not concerned about FINDNODE results, rather the result of an entire + // query. } Discv5Event::SocketUpdated(socket) => { info!(self.log, "Address updated"; "IP" => format!("{}",socket.ip())); @@ -241,6 +215,17 @@ where if closer_peers.is_empty() { debug!(self.log, "Discv5 random query yielded empty results"); } + for peer_id in closer_peers { + // if we need more peers, attempt a connection + if self.connected_peers.len() < self.max_peers + && self.connected_peers.get(&peer_id).is_none() + { + debug!(self.log, "Discv5: Peer discovered"; "Peer"=> format!("{:?}", peer_id)); + return Async::Ready(NetworkBehaviourAction::DialPeer { + peer_id, + }); + } + } } _ => {} } diff --git a/beacon_node/eth2-libp2p/src/service.rs b/beacon_node/eth2-libp2p/src/service.rs index 780b1453f..1db855cd4 100644 --- a/beacon_node/eth2-libp2p/src/service.rs +++ b/beacon_node/eth2-libp2p/src/service.rs @@ -9,6 +9,7 @@ use futures::prelude::*; use futures::Stream; use libp2p::core::{ identity, + multiaddr::Multiaddr, muxing::StreamMuxerBox, nodes::Substream, transport::boxed::Boxed, @@ -52,17 +53,24 @@ impl Service { Swarm::new(transport, behaviour, local_peer_id.clone()) }; - // listen on all addresses - for address in config.listen_addresses { - match Swarm::listen_on(&mut swarm, address.clone()) { - Ok(_) => { - let mut log_address = address.clone(); - log_address.push(Protocol::P2p(local_peer_id.clone().into())); - info!(log, "Listening on: {}", log_address); - } - Err(err) => warn!(log, "Cannot listen on: {} because: {:?}", address, err), - }; - } + // listen on the specified address + let listen_multiaddr = { + let mut m = Multiaddr::from(config.listen_address); + m.push(Protocol::Tcp(config.libp2p_port)); + m + }; + + match Swarm::listen_on(&mut swarm, listen_multiaddr.clone()) { + Ok(_) => { + let mut log_address = listen_multiaddr; + log_address.push(Protocol::P2p(local_peer_id.clone().into())); + info!(log, "Listening on: {}", log_address); + } + Err(err) => warn!( + log, + "Cannot listen on: {} because: {:?}", listen_multiaddr, err + ), + }; // subscribe to default gossipsub topics let mut topics = vec![]; diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 51d3a58f9..f7b92275a 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -33,15 +33,14 @@ fn main() { .arg( Arg::with_name("listen-address") .long("listen-address") - .value_name("Listen Address") - .help("One or more comma-delimited multi-addresses to listen for p2p connections.") + .value_name("Address") + .help("The address lighthouse will listen for UDP and TCP connections. (default 127.0.0.1).") .takes_value(true), ) .arg( Arg::with_name("maxpeers") .long("maxpeers") - .value_name("Max Peers") - .help("The maximum number of peers (default 10)") + .help("The maximum number of peers (default 10).") .takes_value(true), ) .arg( @@ -53,17 +52,24 @@ fn main() { .takes_value(true), ) .arg( - Arg::with_name("disc-listen-address") - .long("disc-listen_address") - .value_name("DISCPORT") - .help("The IP address that the discovery protocol will listen on. Defaults to 0.0.0.0") + Arg::with_name("port") + .long("port") + .value_name("Lighthouse Port") + .help("The TCP/UDP port to listen on. The UDP port can be modified by the --discovery-port flag.") .takes_value(true), ) .arg( Arg::with_name("discovery-port") .long("disc-port") - .value_name("DISCPORT") - .help("Listen UDP port for the discovery process") + .value_name("DiscoveryPort") + .help("The discovery UDP port.") + .takes_value(true), + ) + .arg( + Arg::with_name("discovery-address") + .long("discovery-address") + .value_name("Address") + .help("The address to broadcast to other peers on how to reach this node.") .takes_value(true), ) // rpc related arguments @@ -77,14 +83,13 @@ fn main() { .arg( Arg::with_name("rpc-address") .long("rpc-address") - .value_name("RPCADDRESS") + .value_name("Address") .help("Listen address for RPC endpoint.") .takes_value(true), ) .arg( Arg::with_name("rpc-port") .long("rpc-port") - .value_name("RPCPORT") .help("Listen port for RPC endpoint.") .takes_value(true), ) @@ -92,21 +97,19 @@ fn main() { .arg( Arg::with_name("http") .long("http") - .value_name("HTTP") .help("Enable the HTTP server.") .takes_value(false), ) .arg( Arg::with_name("http-address") .long("http-address") - .value_name("HTTPADDRESS") + .value_name("Address") .help("Listen address for the HTTP server.") .takes_value(true), ) .arg( Arg::with_name("http-port") .long("http-port") - .value_name("HTTPPORT") .help("Listen port for the HTTP server.") .takes_value(true), ) diff --git a/beacon_node/src/run.rs b/beacon_node/src/run.rs index 15883d974..51fa16154 100644 --- a/beacon_node/src/run.rs +++ b/beacon_node/src/run.rs @@ -41,6 +41,15 @@ pub fn run_beacon_node( "This software is EXPERIMENTAL and provides no guarantees or warranties." ); + info!( + log, + "Starting beacon node"; + "p2p_listen_address" => format!("{:?}", &other_client_config.network.listen_address), + "data_dir" => format!("{:?}", other_client_config.data_dir()), + "spec_constants" => &spec_constants, + "db_type" => &other_client_config.db_type, + ); + let result = match (db_type.as_str(), spec_constants.as_str()) { ("disk", "minimal") => run::>( &db_path, @@ -80,17 +89,6 @@ pub fn run_beacon_node( } }; - if result.is_ok() { - info!( - log, - "Started beacon node"; - "p2p_listen_addresses" => format!("{:?}", &other_client_config.network.listen_addresses), - "data_dir" => format!("{:?}", other_client_config.data_dir()), - "spec_constants" => &spec_constants, - "db_type" => &other_client_config.db_type, - ); - } - result }