Add UPnP support for Lighthouse (#1587)
This commit was modified by Paul H whilst rebasing master onto v0.3.0-staging Adding UPnP support will help grow the DHT by allowing NAT traversal for peers with UPnP supported routers. Using IGD library: https://docs.rs/igd/0.10.0/igd/ Adding the the libp2p tcp port and discovery udp port. If this fails it simply logs the attempt and moves on Co-authored-by: Age Manning <Age@AgeManning.com>
This commit is contained in:
parent
8fde9a4016
commit
6af3bc9ce2
41
Cargo.lock
generated
41
Cargo.lock
generated
@ -252,6 +252,17 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0db678acb667b525ac40a324fc5f7d3390e29239b31c7327bb8157f5b4fff593"
|
checksum = "0db678acb667b525ac40a324fc5f7d3390e29239b31c7327bb8157f5b4fff593"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "attohttpc"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf13118df3e3dce4b5ac930641343b91b656e4e72c8f8325838b01a4b1c9d45"
|
||||||
|
dependencies = [
|
||||||
|
"http 0.2.1",
|
||||||
|
"log 0.4.11",
|
||||||
|
"url 2.1.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@ -2523,6 +2534,19 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "igd"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fd32c880165b2f776af0b38d206d1cabaebcf46c166ac6ae004a5d45f7d48ef"
|
||||||
|
dependencies = [
|
||||||
|
"attohttpc",
|
||||||
|
"log 0.4.11",
|
||||||
|
"rand 0.7.3",
|
||||||
|
"url 2.1.1",
|
||||||
|
"xmltree",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "impl-codec"
|
name = "impl-codec"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@ -3469,8 +3493,10 @@ dependencies = [
|
|||||||
"fnv",
|
"fnv",
|
||||||
"futures 0.3.5",
|
"futures 0.3.5",
|
||||||
"genesis",
|
"genesis",
|
||||||
|
"get_if_addrs",
|
||||||
"hashset_delay",
|
"hashset_delay",
|
||||||
"hex 0.4.2",
|
"hex 0.4.2",
|
||||||
|
"igd",
|
||||||
"itertools 0.9.0",
|
"itertools 0.9.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lighthouse_metrics",
|
"lighthouse_metrics",
|
||||||
@ -6746,6 +6772,21 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xmltree"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f76badaccb0313f1f0cb6582c2973f2dd0620f9652eb7a5ff6ced0cc3ac922b3"
|
||||||
|
dependencies = [
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yaml-rust"
|
name = "yaml-rust"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
@ -29,7 +29,7 @@ eth2_wallet = { path = "../crypto/eth2_wallet" }
|
|||||||
eth2_wallet_manager = { path = "../common/eth2_wallet_manager" }
|
eth2_wallet_manager = { path = "../common/eth2_wallet_manager" }
|
||||||
rand = "0.7.2"
|
rand = "0.7.2"
|
||||||
validator_dir = { path = "../common/validator_dir" }
|
validator_dir = { path = "../common/validator_dir" }
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
eth2_keystore = { path = "../crypto/eth2_keystore" }
|
eth2_keystore = { path = "../crypto/eth2_keystore" }
|
||||||
account_utils = { path = "../common/account_utils" }
|
account_utils = { path = "../common/account_utils" }
|
||||||
slashing_protection = { path = "../validator_client/slashing_protection" }
|
slashing_protection = { path = "../validator_client/slashing_protection" }
|
||||||
|
@ -26,7 +26,7 @@ slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_tr
|
|||||||
slog-term = "2.5.0"
|
slog-term = "2.5.0"
|
||||||
slog-async = "2.5.0"
|
slog-async = "2.5.0"
|
||||||
ctrlc = { version = "3.1.4", features = ["termination"] }
|
ctrlc = { version = "3.1.4", features = ["termination"] }
|
||||||
tokio = { version = "0.2.21", features = ["time"] }
|
tokio = { version = "0.2.22", features = ["time"] }
|
||||||
exit-future = "0.2.0"
|
exit-future = "0.2.0"
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
logging = { path = "../common/logging" }
|
logging = { path = "../common/logging" }
|
||||||
|
@ -39,7 +39,7 @@ eth2_ssz_derive = "0.1.0"
|
|||||||
state_processing = { path = "../../consensus/state_processing" }
|
state_processing = { path = "../../consensus/state_processing" }
|
||||||
tree_hash = "0.1.0"
|
tree_hash = "0.1.0"
|
||||||
types = { path = "../../consensus/types" }
|
types = { path = "../../consensus/types" }
|
||||||
tokio = "0.2.21"
|
tokio = "0.2.22"
|
||||||
eth1 = { path = "../eth1" }
|
eth1 = { path = "../eth1" }
|
||||||
websocket_server = { path = "../websocket_server" }
|
websocket_server = { path = "../websocket_server" }
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
|
@ -27,7 +27,7 @@ error-chain = "0.12.2"
|
|||||||
serde_yaml = "0.8.11"
|
serde_yaml = "0.8.11"
|
||||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||||
slog-async = "2.5.0"
|
slog-async = "2.5.0"
|
||||||
tokio = "0.2.21"
|
tokio = "0.2.22"
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
reqwest = { version = "0.10.4", features = ["native-tls-vendored"] }
|
reqwest = { version = "0.10.4", features = ["native-tls-vendored"] }
|
||||||
|
@ -24,7 +24,7 @@ tree_hash = "0.1.0"
|
|||||||
eth2_hashing = "0.1.0"
|
eth2_hashing = "0.1.0"
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
slog = "2.5.2"
|
slog = "2.5.2"
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
state_processing = { path = "../../consensus/state_processing" }
|
state_processing = { path = "../../consensus/state_processing" }
|
||||||
libflate = "1.0.0"
|
libflate = "1.0.0"
|
||||||
lighthouse_metrics = { path = "../../common/lighthouse_metrics"}
|
lighthouse_metrics = { path = "../../common/lighthouse_metrics"}
|
||||||
|
@ -15,7 +15,7 @@ eth2_ssz = "0.1.2"
|
|||||||
eth2_ssz_derive = "0.1.0"
|
eth2_ssz_derive = "0.1.0"
|
||||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||||
lighthouse_version = { path = "../../common/lighthouse_version" }
|
lighthouse_version = { path = "../../common/lighthouse_version" }
|
||||||
tokio = { version = "0.2.21", features = ["time", "macros"] }
|
tokio = { version = "0.2.22", features = ["time", "macros"] }
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
error-chain = "0.12.2"
|
error-chain = "0.12.2"
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
@ -47,7 +47,7 @@ default-features = false
|
|||||||
features = ["websocket", "identify", "mplex", "noise", "gossipsub", "dns", "tcp-tokio"]
|
features = ["websocket", "identify", "mplex", "noise", "gossipsub", "dns", "tcp-tokio"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
slog-stdlog = "4.0.0"
|
slog-stdlog = "4.0.0"
|
||||||
slog-term = "2.5.0"
|
slog-term = "2.5.0"
|
||||||
slog-async = "2.5.0"
|
slog-async = "2.5.0"
|
||||||
|
@ -70,6 +70,9 @@ pub struct Config {
|
|||||||
/// Disables the discovery protocol from starting.
|
/// Disables the discovery protocol from starting.
|
||||||
pub disable_discovery: bool,
|
pub disable_discovery: bool,
|
||||||
|
|
||||||
|
/// Attempt to construct external port mappings with UPnP.
|
||||||
|
pub upnp_enabled: bool,
|
||||||
|
|
||||||
/// List of extra topics to initially subscribe to as strings.
|
/// List of extra topics to initially subscribe to as strings.
|
||||||
pub topics: Vec<GossipKind>,
|
pub topics: Vec<GossipKind>,
|
||||||
}
|
}
|
||||||
@ -144,6 +147,7 @@ impl Default for Config {
|
|||||||
trusted_peers: vec![],
|
trusted_peers: vec![],
|
||||||
client_version: lighthouse_version::version_with_platform(),
|
client_version: lighthouse_version::version_with_platform(),
|
||||||
disable_discovery: false,
|
disable_discovery: false,
|
||||||
|
upnp_enabled: true,
|
||||||
topics: Vec::new(),
|
topics: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ pub struct Discovery<TSpec: EthSpec> {
|
|||||||
|
|
||||||
/// Indicates if the discovery service has been started. When the service is disabled, this is
|
/// Indicates if the discovery service has been started. When the service is disabled, this is
|
||||||
/// always false.
|
/// always false.
|
||||||
started: bool,
|
pub started: bool,
|
||||||
|
|
||||||
/// Logger for the discovery behaviour.
|
/// Logger for the discovery behaviour.
|
||||||
log: slog::Logger,
|
log: slog::Logger,
|
||||||
@ -358,6 +358,54 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the local ENR TCP port.
|
||||||
|
/// There currently isn't a case to update the address here. We opt for discovery to
|
||||||
|
/// automatically update the external address.
|
||||||
|
///
|
||||||
|
/// If the external address needs to be modified, use `update_enr_udp_socket.
|
||||||
|
pub fn update_enr_tcp_port(&mut self, port: u16) -> Result<(), String> {
|
||||||
|
self.discv5
|
||||||
|
.enr_insert("tcp", port.to_be_bytes().into())
|
||||||
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
|
// replace the global version
|
||||||
|
*self.network_globals.local_enr.write() = self.discv5.local_enr();
|
||||||
|
// persist modified enr to disk
|
||||||
|
enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the local ENR UDP socket.
|
||||||
|
///
|
||||||
|
/// This is with caution. Discovery should automatically maintain this. This should only be
|
||||||
|
/// used when automatic discovery is disabled.
|
||||||
|
pub fn update_enr_udp_socket(&mut self, socket_addr: SocketAddr) -> Result<(), String> {
|
||||||
|
match socket_addr {
|
||||||
|
SocketAddr::V4(socket) => {
|
||||||
|
self.discv5
|
||||||
|
.enr_insert("ip", socket.ip().octets().into())
|
||||||
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
self.discv5
|
||||||
|
.enr_insert("udp", socket.port().to_be_bytes().into())
|
||||||
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
}
|
||||||
|
SocketAddr::V6(socket) => {
|
||||||
|
self.discv5
|
||||||
|
.enr_insert("ip6", socket.ip().octets().into())
|
||||||
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
self.discv5
|
||||||
|
.enr_insert("udp6", socket.port().to_be_bytes().into())
|
||||||
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace the global version
|
||||||
|
*self.network_globals.local_enr.write() = self.discv5.local_enr();
|
||||||
|
// persist modified enr to disk
|
||||||
|
enr::save_enr_to_disk(Path::new(&self.enr_dir), &self.local_enr(), &self.log);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds/Removes a subnet from the ENR Bitfield
|
/// Adds/Removes a subnet from the ENR Bitfield
|
||||||
pub fn update_enr_bitfield(&mut self, subnet_id: SubnetId, value: bool) -> Result<(), String> {
|
pub fn update_enr_bitfield(&mut self, subnet_id: SubnetId, value: bool) -> Result<(), String> {
|
||||||
let id = *subnet_id as usize;
|
let id = *subnet_id as usize;
|
||||||
@ -390,9 +438,9 @@ impl<TSpec: EthSpec> Discovery<TSpec> {
|
|||||||
.map_err(|_| String::from("Subnet ID out of bounds, could not set subnet ID"))?;
|
.map_err(|_| String::from("Subnet ID out of bounds, could not set subnet ID"))?;
|
||||||
|
|
||||||
// insert the bitfield into the ENR record
|
// insert the bitfield into the ENR record
|
||||||
let _ = self
|
self.discv5
|
||||||
.discv5
|
.enr_insert(BITFIELD_ENR_KEY, current_bitfield.as_ssz_bytes())
|
||||||
.enr_insert(BITFIELD_ENR_KEY, current_bitfield.as_ssz_bytes());
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
// replace the global version
|
// replace the global version
|
||||||
*self.network_globals.local_enr.write() = self.discv5.local_enr();
|
*self.network_globals.local_enr.write() = self.discv5.local_enr();
|
||||||
|
@ -18,7 +18,7 @@ merkle_proof = { path = "../../consensus/merkle_proof" }
|
|||||||
eth2_ssz = "0.1.2"
|
eth2_ssz = "0.1.2"
|
||||||
eth2_hashing = "0.1.0"
|
eth2_hashing = "0.1.0"
|
||||||
tree_hash = "0.1.0"
|
tree_hash = "0.1.0"
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
slog = "2.5.2"
|
slog = "2.5.2"
|
||||||
exit-future = "0.2.0"
|
exit-future = "0.2.0"
|
||||||
|
@ -9,7 +9,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
warp = "0.2.5"
|
warp = "0.2.5"
|
||||||
serde = { version = "1.0.110", features = ["derive"] }
|
serde = { version = "1.0.110", features = ["derive"] }
|
||||||
tokio = { version = "0.2.21", features = ["sync"] }
|
tokio = { version = "0.2.22", features = ["macros"] }
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
types = { path = "../../consensus/types" }
|
types = { path = "../../consensus/types" }
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
|
@ -27,17 +27,17 @@ eth2_ssz_types = { path = "../../consensus/ssz_types" }
|
|||||||
tree_hash = "0.1.0"
|
tree_hash = "0.1.0"
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
error-chain = "0.12.2"
|
error-chain = "0.12.2"
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
smallvec = "1.4.1"
|
smallvec = "1.4.1"
|
||||||
# TODO: Remove rand crate for mainnet
|
|
||||||
# NOTE: why?
|
|
||||||
rand = "0.7.3"
|
rand = "0.7.3"
|
||||||
fnv = "1.0.6"
|
fnv = "1.0.6"
|
||||||
rlp = "0.4.5"
|
rlp = "0.4.5"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
|
lighthouse_metrics = { path = "../../common/lighthouse_metrics" }
|
||||||
environment = { path = "../../lighthouse/environment" }
|
environment = { path = "../../lighthouse/environment" }
|
||||||
|
igd = "0.11.1"
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
num_cpus = "1.13.0"
|
num_cpus = "1.13.0"
|
||||||
lru_cache = { path = "../../common/lru_cache" }
|
lru_cache = { path = "../../common/lru_cache" }
|
||||||
|
get_if_addrs = "0.5.3"
|
||||||
|
@ -8,6 +8,7 @@ pub mod service;
|
|||||||
mod attestation_service;
|
mod attestation_service;
|
||||||
mod beacon_processor;
|
mod beacon_processor;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
|
mod nat;
|
||||||
mod persisted_dht;
|
mod persisted_dht;
|
||||||
mod router;
|
mod router;
|
||||||
mod sync;
|
mod sync;
|
||||||
|
154
beacon_node/network/src/nat.rs
Normal file
154
beacon_node/network/src/nat.rs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
//! This houses various NAT hole punching strategies.
|
||||||
|
//!
|
||||||
|
//! Currently supported strategies:
|
||||||
|
//! - UPnP
|
||||||
|
|
||||||
|
use crate::{NetworkConfig, NetworkMessage};
|
||||||
|
use get_if_addrs::get_if_addrs;
|
||||||
|
use slog::{debug, info, warn};
|
||||||
|
use std::net::{IpAddr, SocketAddr, SocketAddrV4};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use types::EthSpec;
|
||||||
|
|
||||||
|
/// Configuration required to construct the UPnP port mappings.
|
||||||
|
pub struct UPnPConfig {
|
||||||
|
/// The local tcp port.
|
||||||
|
tcp_port: u16,
|
||||||
|
/// The local udp port.
|
||||||
|
udp_port: u16,
|
||||||
|
/// Whether discovery is enabled or not.
|
||||||
|
disable_discovery: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&NetworkConfig> for UPnPConfig {
|
||||||
|
fn from(config: &NetworkConfig) -> Self {
|
||||||
|
UPnPConfig {
|
||||||
|
tcp_port: config.libp2p_port,
|
||||||
|
udp_port: config.discovery_port,
|
||||||
|
disable_discovery: config.disable_discovery,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to construct external port mappings with UPnP.
|
||||||
|
pub fn construct_upnp_mappings<T: EthSpec>(
|
||||||
|
config: UPnPConfig,
|
||||||
|
network_send: mpsc::UnboundedSender<NetworkMessage<T>>,
|
||||||
|
log: slog::Logger,
|
||||||
|
) {
|
||||||
|
debug!(log, "UPnP Initialising routes");
|
||||||
|
match igd::search_gateway(Default::default()) {
|
||||||
|
Err(e) => debug!(log, "UPnP not available"; "error" => %e),
|
||||||
|
Ok(gateway) => {
|
||||||
|
// Need to find the local listening address matched with the router subnet
|
||||||
|
let mut local_ip = None;
|
||||||
|
let interfaces = match get_if_addrs() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
debug!(log, "UPnP failed to get local interfaces"; "error" => %e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for interface in interfaces {
|
||||||
|
// Just use the first IP of the first interface that is not a loopback
|
||||||
|
if !interface.is_loopback() {
|
||||||
|
local_ip = Some(interface.ip());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if local_ip.is_none() {
|
||||||
|
debug!(log, "UPnP failed to find local IP address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let local_ip = local_ip.expect("IP exists");
|
||||||
|
|
||||||
|
match local_ip {
|
||||||
|
IpAddr::V4(address) => {
|
||||||
|
let libp2p_socket = SocketAddrV4::new(address, config.tcp_port);
|
||||||
|
let external_ip = gateway.get_external_ip();
|
||||||
|
// We add specific port mappings rather than getting the router to arbitrary assign
|
||||||
|
// one.
|
||||||
|
// I've found this to be more reliable. If multiple users are behind a single
|
||||||
|
// router, they should ideally try to set different port numbers.
|
||||||
|
let tcp_socket = match gateway.add_port(
|
||||||
|
igd::PortMappingProtocol::TCP,
|
||||||
|
libp2p_socket.port(),
|
||||||
|
libp2p_socket,
|
||||||
|
0,
|
||||||
|
"lighthouse-tcp",
|
||||||
|
) {
|
||||||
|
Err(e) => {
|
||||||
|
debug!(log, "UPnP could not construct libp2p port route"; "error" => %e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
info!(log, "UPnP TCP route established"; "external_socket" => format!("{}:{}", external_ip.as_ref().map(|ip| ip.to_string()).unwrap_or_else(|_| "".into()), config.tcp_port));
|
||||||
|
external_ip
|
||||||
|
.as_ref()
|
||||||
|
.map(|ip| SocketAddr::new(ip.clone().into(), config.tcp_port))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let udp_socket = if !config.disable_discovery {
|
||||||
|
let discovery_socket = SocketAddrV4::new(address, config.udp_port);
|
||||||
|
match gateway.add_port(
|
||||||
|
igd::PortMappingProtocol::UDP,
|
||||||
|
discovery_socket.port(),
|
||||||
|
discovery_socket,
|
||||||
|
0,
|
||||||
|
"lighthouse-udp",
|
||||||
|
) {
|
||||||
|
Err(e) => {
|
||||||
|
debug!(log, "UPnP could not construct discovery port route"; "error" => %e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
info!(log, "UPnP UDP route established"; "external_socket" => format!("{}:{}", external_ip.as_ref().map(|ip| ip.to_string()).unwrap_or_else(|_| "".into()), config.tcp_port));
|
||||||
|
external_ip
|
||||||
|
.map(|ip| SocketAddr::new(ip.into(), config.tcp_port))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// report any updates to the network service.
|
||||||
|
network_send.send(NetworkMessage::UPnPMappingEstablished{ tcp_socket, udp_socket })
|
||||||
|
.unwrap_or_else(|e| warn!(log, "Could not send message to the network service"; "error" => %e));
|
||||||
|
}
|
||||||
|
_ => debug!(log, "UPnP no routes constructed. IPv6 not supported"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the specified TCP and UDP port mappings.
|
||||||
|
pub fn remove_mappings(tcp_port: Option<u16>, udp_port: Option<u16>, log: &slog::Logger) {
|
||||||
|
if tcp_port.is_some() || udp_port.is_some() {
|
||||||
|
debug!(log, "Removing UPnP port mappings");
|
||||||
|
match igd::search_gateway(Default::default()) {
|
||||||
|
Ok(gateway) => {
|
||||||
|
if let Some(tcp_port) = tcp_port {
|
||||||
|
match gateway.remove_port(igd::PortMappingProtocol::TCP, tcp_port) {
|
||||||
|
Ok(()) => debug!(log, "UPnP Removed TCP port mapping"; "port" => tcp_port),
|
||||||
|
Err(e) => {
|
||||||
|
debug!(log, "UPnP Failed to remove TCP port mapping"; "port" => tcp_port, "error" => %e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(udp_port) = udp_port {
|
||||||
|
match gateway.remove_port(igd::PortMappingProtocol::UDP, udp_port) {
|
||||||
|
Ok(()) => debug!(log, "UPnP Removed UDP port mapping"; "port" => udp_port),
|
||||||
|
Err(e) => {
|
||||||
|
debug!(log, "UPnP Failed to remove UDP port mapping"; "port" => udp_port, "error" => %e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => debug!(log, "UPnP failed to remove mappings"; "error" => %e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ use eth2_libp2p::{
|
|||||||
use eth2_libp2p::{MessageAcceptance, Service as LibP2PService};
|
use eth2_libp2p::{MessageAcceptance, Service as LibP2PService};
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use slog::{debug, error, info, o, trace, warn};
|
use slog::{debug, error, info, o, trace, warn};
|
||||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
|
||||||
use store::HotColdDB;
|
use store::HotColdDB;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::time::Delay;
|
use tokio::time::Delay;
|
||||||
@ -69,6 +69,13 @@ pub enum NetworkMessage<T: EthSpec> {
|
|||||||
/// The result of the validation
|
/// The result of the validation
|
||||||
validation_result: MessageAcceptance,
|
validation_result: MessageAcceptance,
|
||||||
},
|
},
|
||||||
|
/// Called if a known external TCP socket address has been updated.
|
||||||
|
UPnPMappingEstablished {
|
||||||
|
/// The external TCP address has been updated.
|
||||||
|
tcp_socket: Option<SocketAddr>,
|
||||||
|
/// The external UDP address has been updated.
|
||||||
|
udp_socket: Option<SocketAddr>,
|
||||||
|
},
|
||||||
/// Reports a peer to the peer manager for performing an action.
|
/// Reports a peer to the peer manager for performing an action.
|
||||||
ReportPeer { peer_id: PeerId, action: PeerAction },
|
ReportPeer { peer_id: PeerId, action: PeerAction },
|
||||||
/// Disconnect an ban a peer, providing a reason.
|
/// Disconnect an ban a peer, providing a reason.
|
||||||
@ -95,6 +102,12 @@ pub struct NetworkService<T: BeaconChainTypes> {
|
|||||||
store: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
|
store: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
|
||||||
/// A collection of global variables, accessible outside of the network service.
|
/// A collection of global variables, accessible outside of the network service.
|
||||||
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
network_globals: Arc<NetworkGlobals<T::EthSpec>>,
|
||||||
|
/// Stores potentially created UPnP mappings to be removed on shutdown. (TCP port and UDP
|
||||||
|
/// port).
|
||||||
|
upnp_mappings: (Option<u16>, Option<u16>),
|
||||||
|
/// Keeps track of if discovery is auto-updating or not. This is used to inform us if we should
|
||||||
|
/// update the UDP socket of discovery if the UPnP mappings get established.
|
||||||
|
discovery_auto_update: bool,
|
||||||
/// A delay that expires when a new fork takes place.
|
/// A delay that expires when a new fork takes place.
|
||||||
next_fork_update: Option<Delay>,
|
next_fork_update: Option<Delay>,
|
||||||
/// A timer for updating various network metrics.
|
/// A timer for updating various network metrics.
|
||||||
@ -116,6 +129,20 @@ impl<T: BeaconChainTypes> NetworkService<T> {
|
|||||||
let network_log = executor.log().clone();
|
let network_log = executor.log().clone();
|
||||||
// build the network channel
|
// build the network channel
|
||||||
let (network_send, network_recv) = mpsc::unbounded_channel::<NetworkMessage<T::EthSpec>>();
|
let (network_send, network_recv) = mpsc::unbounded_channel::<NetworkMessage<T::EthSpec>>();
|
||||||
|
|
||||||
|
// try and construct UPnP port mappings if required.
|
||||||
|
let upnp_config = crate::nat::UPnPConfig::from(config);
|
||||||
|
let upnp_log = network_log.new(o!("service" => "UPnP"));
|
||||||
|
let upnp_network_send = network_send.clone();
|
||||||
|
if config.upnp_enabled {
|
||||||
|
executor.spawn_blocking(
|
||||||
|
move || {
|
||||||
|
crate::nat::construct_upnp_mappings(upnp_config, upnp_network_send, upnp_log)
|
||||||
|
},
|
||||||
|
"UPnP",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// get a reference to the beacon chain store
|
// get a reference to the beacon chain store
|
||||||
let store = beacon_chain.store.clone();
|
let store = beacon_chain.store.clone();
|
||||||
|
|
||||||
@ -166,6 +193,8 @@ impl<T: BeaconChainTypes> NetworkService<T> {
|
|||||||
router_send,
|
router_send,
|
||||||
store,
|
store,
|
||||||
network_globals: network_globals.clone(),
|
network_globals: network_globals.clone(),
|
||||||
|
upnp_mappings: (None, None),
|
||||||
|
discovery_auto_update: config.discv5_config.enr_update,
|
||||||
next_fork_update,
|
next_fork_update,
|
||||||
metrics_update,
|
metrics_update,
|
||||||
log: network_log,
|
log: network_log,
|
||||||
@ -200,7 +229,6 @@ fn spawn_service<T: BeaconChainTypes>(
|
|||||||
"Persisting DHT to store";
|
"Persisting DHT to store";
|
||||||
"Number of peers" => format!("{}", enrs.len()),
|
"Number of peers" => format!("{}", enrs.len()),
|
||||||
);
|
);
|
||||||
|
|
||||||
match persist_dht::<T::EthSpec, T::HotStore, T::ColdStore>(service.store.clone(), enrs) {
|
match persist_dht::<T::EthSpec, T::HotStore, T::ColdStore>(service.store.clone(), enrs) {
|
||||||
Err(e) => error!(
|
Err(e) => error!(
|
||||||
service.log,
|
service.log,
|
||||||
@ -213,6 +241,9 @@ fn spawn_service<T: BeaconChainTypes>(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// attempt to remove port mappings
|
||||||
|
crate::nat::remove_mappings(service.upnp_mappings.0, service.upnp_mappings.1, &service.log);
|
||||||
|
|
||||||
info!(service.log, "Network service shutdown");
|
info!(service.log, "Network service shutdown");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -239,6 +270,24 @@ fn spawn_service<T: BeaconChainTypes>(
|
|||||||
NetworkMessage::SendError{ peer_id, error, id, reason } => {
|
NetworkMessage::SendError{ peer_id, error, id, reason } => {
|
||||||
service.libp2p.respond_with_error(peer_id, id, error, reason);
|
service.libp2p.respond_with_error(peer_id, id, error, reason);
|
||||||
}
|
}
|
||||||
|
NetworkMessage::UPnPMappingEstablished { tcp_socket, udp_socket} => {
|
||||||
|
service.upnp_mappings = (tcp_socket.map(|s| s.port()), udp_socket.map(|s| s.port()));
|
||||||
|
// If there is an external TCP port update, modify our local ENR.
|
||||||
|
if let Some(tcp_socket) = tcp_socket {
|
||||||
|
if let Err(e) = service.libp2p.swarm.peer_manager().discovery_mut().update_enr_tcp_port(tcp_socket.port()) {
|
||||||
|
warn!(service.log, "Failed to update ENR"; "error" => e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the discovery service is not auto-updating, update it with the
|
||||||
|
// UPnP mappings
|
||||||
|
if !service.discovery_auto_update {
|
||||||
|
if let Some(udp_socket) = udp_socket {
|
||||||
|
if let Err(e) = service.libp2p.swarm.peer_manager().discovery_mut().update_enr_udp_socket(udp_socket) {
|
||||||
|
warn!(service.log, "Failed to update ENR"; "error" => e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
NetworkMessage::ValidationResult {
|
NetworkMessage::ValidationResult {
|
||||||
propagation_source,
|
propagation_source,
|
||||||
message_id,
|
message_id,
|
||||||
|
@ -75,6 +75,12 @@ pub fn cli_app<'a, 'b>() -> App<'a, 'b> {
|
|||||||
.help("One or more comma-delimited base64-encoded ENR's to bootstrap the p2p network. Multiaddr is also supported.")
|
.help("One or more comma-delimited base64-encoded ENR's to bootstrap the p2p network. Multiaddr is also supported.")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("disable-upnp")
|
||||||
|
.long("disable-upnp")
|
||||||
|
.help("Disables UPnP support. Setting this will prevent Lighthouse from attempting to automatically establish external port mappings.")
|
||||||
|
.takes_value(false),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("enr-udp-port")
|
Arg::with_name("enr-udp-port")
|
||||||
.long("enr-udp-port")
|
.long("enr-udp-port")
|
||||||
|
@ -507,7 +507,7 @@ pub fn set_network_config(
|
|||||||
config.enr_address = Some(resolved_addr);
|
config.enr_address = Some(resolved_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli_args.is_present("disable_enr_auto_update") {
|
if cli_args.is_present("disable-enr-auto-update") {
|
||||||
config.discv5_config.enr_update = false;
|
config.discv5_config.enr_update = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,6 +516,10 @@ pub fn set_network_config(
|
|||||||
warn!(log, "Discovery is disabled. New peers will not be found");
|
warn!(log, "Discovery is disabled. New peers will not be found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cli_args.is_present("disable-upnp") {
|
||||||
|
config.upnp_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ edition = "2018"
|
|||||||
beacon_chain = { path = "../beacon_chain" }
|
beacon_chain = { path = "../beacon_chain" }
|
||||||
types = { path = "../../consensus/types" }
|
types = { path = "../../consensus/types" }
|
||||||
slot_clock = { path = "../../common/slot_clock" }
|
slot_clock = { path = "../../common/slot_clock" }
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
slog = "2.5.2"
|
slog = "2.5.2"
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
|
@ -12,7 +12,7 @@ serde = "1.0.110"
|
|||||||
serde_derive = "1.0.110"
|
serde_derive = "1.0.110"
|
||||||
serde_json = "1.0.52"
|
serde_json = "1.0.52"
|
||||||
slog = "2.5.2"
|
slog = "2.5.2"
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
types = { path = "../../consensus/types" }
|
types = { path = "../../consensus/types" }
|
||||||
ws = "0.9.1"
|
ws = "0.9.1"
|
||||||
environment = { path = "../../lighthouse/environment" }
|
environment = { path = "../../lighthouse/environment" }
|
||||||
|
@ -13,7 +13,7 @@ eth2_testnet_config = { path = "../common/eth2_testnet_config" }
|
|||||||
eth2_ssz = { path = "../consensus/ssz" }
|
eth2_ssz = { path = "../consensus/ssz" }
|
||||||
slog = "2.5.2"
|
slog = "2.5.2"
|
||||||
sloggers = "1.0.1"
|
sloggers = "1.0.1"
|
||||||
tokio = "0.2.21"
|
tokio = "0.2.22"
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
slog-term = "2.6.0"
|
slog-term = "2.6.0"
|
||||||
logging = { path = "../common/logging" }
|
logging = { path = "../common/logging" }
|
||||||
|
@ -6,7 +6,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
tokio = { version = "0.2.21", features = ["time"] }
|
tokio = { version = "0.2.22", features = ["time"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "0.2.21", features = ["time", "rt-threaded", "macros"] }
|
tokio = { version = "0.2.22", features = ["time", "rt-threaded", "macros"] }
|
||||||
|
27
common/rest_types/Cargo.toml
Normal file
27
common/rest_types/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "rest_types"
|
||||||
|
version = "0.2.0"
|
||||||
|
authors = ["Sigma Prime <contact@sigmaprime.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
types = { path = "../../consensus/types" }
|
||||||
|
eth2_ssz_derive = "0.1.0"
|
||||||
|
eth2_ssz = "0.1.2"
|
||||||
|
eth2_hashing = "0.1.0"
|
||||||
|
tree_hash = "0.1.0"
|
||||||
|
state_processing = { path = "../../consensus/state_processing" }
|
||||||
|
bls = { path = "../../crypto/bls" }
|
||||||
|
serde = { version = "1.0.110", features = ["derive"] }
|
||||||
|
rayon = "1.3.0"
|
||||||
|
hyper = "0.13.5"
|
||||||
|
tokio = { version = "0.2.22", features = ["sync"] }
|
||||||
|
environment = { path = "../../lighthouse/environment" }
|
||||||
|
store = { path = "../../beacon_node/store" }
|
||||||
|
beacon_chain = { path = "../../beacon_node/beacon_chain" }
|
||||||
|
serde_json = "1.0.52"
|
||||||
|
serde_yaml = "0.8.11"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
psutil = "3.1.0"
|
||||||
|
procinfo = "0.4.2"
|
@ -28,7 +28,7 @@ dirs = "2.0.2"
|
|||||||
genesis = { path = "../beacon_node/genesis" }
|
genesis = { path = "../beacon_node/genesis" }
|
||||||
deposit_contract = { path = "../common/deposit_contract" }
|
deposit_contract = { path = "../common/deposit_contract" }
|
||||||
tree_hash = "0.1.0"
|
tree_hash = "0.1.0"
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["full"] }
|
||||||
clap_utils = { path = "../common/clap_utils" }
|
clap_utils = { path = "../common/clap_utils" }
|
||||||
eth2_libp2p = { path = "../beacon_node/eth2_libp2p" }
|
eth2_libp2p = { path = "../beacon_node/eth2_libp2p" }
|
||||||
validator_dir = { path = "../common/validator_dir", features = ["insecure_keys"] }
|
validator_dir = { path = "../common/validator_dir", features = ["insecure_keys"] }
|
||||||
|
@ -14,7 +14,7 @@ milagro = ["bls/milagro"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
beacon_node = { "path" = "../beacon_node" }
|
beacon_node = { "path" = "../beacon_node" }
|
||||||
tokio = "0.2.21"
|
tokio = "0.2.22"
|
||||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||||
sloggers = "1.0.0"
|
sloggers = "1.0.0"
|
||||||
types = { "path" = "../consensus/types" }
|
types = { "path" = "../consensus/types" }
|
||||||
|
@ -5,7 +5,7 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "0.2.21", features = ["full"] }
|
tokio = { version = "0.2.22", features = ["macros"] }
|
||||||
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
slog = { version = "2.5.2", features = ["max_level_trace"] }
|
||||||
sloggers = "1.0.0"
|
sloggers = "1.0.0"
|
||||||
types = { "path" = "../../consensus/types" }
|
types = { "path" = "../../consensus/types" }
|
||||||
|
@ -30,6 +30,8 @@ mod metrics;
|
|||||||
|
|
||||||
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml";
|
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml";
|
||||||
const LOG_CHANNEL_SIZE: usize = 2048;
|
const LOG_CHANNEL_SIZE: usize = 2048;
|
||||||
|
/// The maximum time in seconds the client will wait for all internal tasks to shutdown.
|
||||||
|
const MAXIMUM_SHUTDOWN_TIME: u64 = 3;
|
||||||
|
|
||||||
/// Builds an `Environment`.
|
/// Builds an `Environment`.
|
||||||
pub struct EnvironmentBuilder<E: EthSpec> {
|
pub struct EnvironmentBuilder<E: EthSpec> {
|
||||||
@ -424,7 +426,7 @@ impl<E: EthSpec> Environment<E> {
|
|||||||
/// Shutdown the `tokio` runtime when all tasks are idle.
|
/// Shutdown the `tokio` runtime when all tasks are idle.
|
||||||
pub fn shutdown_on_idle(self) {
|
pub fn shutdown_on_idle(self) {
|
||||||
self.runtime
|
self.runtime
|
||||||
.shutdown_timeout(std::time::Duration::from_secs(2))
|
.shutdown_timeout(std::time::Duration::from_secs(MAXIMUM_SHUTDOWN_TIME))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fire exit signal which shuts down all spawned services
|
/// Fire exit signal which shuts down all spawned services
|
||||||
|
@ -5,7 +5,7 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio = { version = "0.2.21", features = ["time"] }
|
tokio = { version = "0.2.22", features = ["time"] }
|
||||||
web3 = "0.11.0"
|
web3 = "0.11.0"
|
||||||
futures = { version = "0.3.5", features = ["compat"] }
|
futures = { version = "0.3.5", features = ["compat"] }
|
||||||
types = { path = "../../consensus/types"}
|
types = { path = "../../consensus/types"}
|
||||||
|
@ -13,7 +13,7 @@ types = { path = "../../consensus/types" }
|
|||||||
validator_client = { path = "../../validator_client" }
|
validator_client = { path = "../../validator_client" }
|
||||||
parking_lot = "0.11.0"
|
parking_lot = "0.11.0"
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
tokio = "0.2.21"
|
tokio = "0.2.22"
|
||||||
eth1_test_rig = { path = "../eth1_test_rig" }
|
eth1_test_rig = { path = "../eth1_test_rig" }
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.7.1"
|
||||||
clap = "2.33.0"
|
clap = "2.33.0"
|
||||||
|
@ -9,7 +9,7 @@ name = "validator_client"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "0.2.21", features = ["time", "rt-threaded", "macros"] }
|
tokio = { version = "0.2.22", features = ["time", "rt-threaded", "macros"] }
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
deposit_contract = { path = "../common/deposit_contract" }
|
deposit_contract = { path = "../common/deposit_contract" }
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ serde_yaml = "0.8.13"
|
|||||||
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
|
slog = { version = "2.5.2", features = ["max_level_trace", "release_max_level_trace"] }
|
||||||
slog-async = "2.5.0"
|
slog-async = "2.5.0"
|
||||||
slog-term = "2.5.0"
|
slog-term = "2.5.0"
|
||||||
tokio = { version = "0.2.21", features = ["time"] }
|
tokio = { version = "0.2.22", features = ["time"] }
|
||||||
futures = { version = "0.3.5", features = ["compat"] }
|
futures = { version = "0.3.5", features = ["compat"] }
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
directory = {path = "../common/directory"}
|
directory = {path = "../common/directory"}
|
||||||
|
Loading…
Reference in New Issue
Block a user