Add support for noise protocol (#873)
* Add noise support with fallback to secio * Add config parameter for noise support * Add secio/noise compatibility test * Cleanup * Remove config parameter for noise support * Modify test to work between a secio swarm and a noise libp2p service * Minor fixes
This commit is contained in:
parent
0c96c515a0
commit
4d60694443
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1132,6 +1132,7 @@ dependencies = [
|
|||||||
"slog-stdlog 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slog-stdlog 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"slog-term 2.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"slog-term 2.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tokio-io-timeout 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tokio-io-timeout 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"types 0.1.0",
|
"types 0.1.0",
|
||||||
|
@ -36,3 +36,4 @@ base64 = "0.11.0"
|
|||||||
slog-stdlog = "4.0.0"
|
slog-stdlog = "4.0.0"
|
||||||
slog-term = "2.4.2"
|
slog-term = "2.4.2"
|
||||||
slog-async = "2.3.0"
|
slog-async = "2.3.0"
|
||||||
|
tempdir = "0.3"
|
||||||
|
@ -7,11 +7,16 @@ use crate::{NetworkGlobals, Topic, TopicHash};
|
|||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use libp2p::core::{
|
use libp2p::core::{
|
||||||
identity::Keypair, multiaddr::Multiaddr, muxing::StreamMuxerBox, nodes::Substream,
|
identity::Keypair,
|
||||||
transport::boxed::Boxed, ConnectedPoint,
|
multiaddr::Multiaddr,
|
||||||
|
muxing::StreamMuxerBox,
|
||||||
|
nodes::Substream,
|
||||||
|
transport::boxed::Boxed,
|
||||||
|
upgrade::{InboundUpgradeExt, OutboundUpgradeExt},
|
||||||
|
ConnectedPoint,
|
||||||
};
|
};
|
||||||
use libp2p::gossipsub::MessageId;
|
use libp2p::gossipsub::MessageId;
|
||||||
use libp2p::{core, secio, swarm::NetworkBehaviour, PeerId, Swarm, Transport};
|
use libp2p::{core, noise, secio, swarm::NetworkBehaviour, PeerId, Swarm, Transport};
|
||||||
use slog::{crit, debug, error, info, trace, warn};
|
use slog::{crit, debug, error, info, trace, warn};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
@ -68,7 +73,7 @@ impl Service {
|
|||||||
let network_globals = Arc::new(NetworkGlobals::new(local_peer_id.clone()));
|
let network_globals = Arc::new(NetworkGlobals::new(local_peer_id.clone()));
|
||||||
|
|
||||||
let mut swarm = {
|
let mut swarm = {
|
||||||
// Set up the transport - tcp/ws with secio and mplex/yamux
|
// Set up the transport - tcp/ws with noise/secio and mplex/yamux
|
||||||
let transport = build_transport(local_keypair.clone());
|
let transport = build_transport(local_keypair.clone());
|
||||||
// Lighthouse network behaviour
|
// Lighthouse network behaviour
|
||||||
let behaviour = Behaviour::new(&local_keypair, config, network_globals.clone(), &log)?;
|
let behaviour = Behaviour::new(&local_keypair, config, network_globals.clone(), &log)?;
|
||||||
@ -250,7 +255,7 @@ impl Stream for Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The implementation supports TCP/IP, WebSockets over TCP/IP, secio as the encryption layer, and
|
/// The implementation supports TCP/IP, WebSockets over TCP/IP, noise/secio as the encryption layer, and
|
||||||
/// mplex or yamux as the multiplexing layer.
|
/// mplex or yamux as the multiplexing layer.
|
||||||
fn build_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox), Error> {
|
fn build_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox), Error> {
|
||||||
// TODO: The Wire protocol currently doesn't specify encryption and this will need to be customised
|
// TODO: The Wire protocol currently doesn't specify encryption and this will need to be customised
|
||||||
@ -262,20 +267,51 @@ fn build_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox)
|
|||||||
let trans_clone = transport.clone();
|
let trans_clone = transport.clone();
|
||||||
transport.or_transport(websocket::WsConfig::new(trans_clone))
|
transport.or_transport(websocket::WsConfig::new(trans_clone))
|
||||||
};
|
};
|
||||||
transport
|
// Authentication
|
||||||
.upgrade(core::upgrade::Version::V1)
|
let transport = transport
|
||||||
.authenticate(secio::SecioConfig::new(local_private_key))
|
.and_then(move |stream, endpoint| {
|
||||||
.multiplex(core::upgrade::SelectUpgrade::new(
|
let upgrade = core::upgrade::SelectUpgrade::new(
|
||||||
libp2p::yamux::Config::default(),
|
generate_noise_config(&local_private_key),
|
||||||
libp2p::mplex::MplexConfig::new(),
|
secio::SecioConfig::new(local_private_key),
|
||||||
))
|
);
|
||||||
.map(|(peer, muxer), _| (peer, core::muxing::StreamMuxerBox::new(muxer)))
|
core::upgrade::apply(stream, upgrade, endpoint, core::upgrade::Version::V1).and_then(
|
||||||
.timeout(Duration::from_secs(20))
|
move |out| {
|
||||||
|
match out {
|
||||||
|
// Noise was negotiated
|
||||||
|
core::either::EitherOutput::First((remote_id, out)) => {
|
||||||
|
Ok((core::either::EitherOutput::First(out), remote_id))
|
||||||
|
}
|
||||||
|
// Secio was negotiated
|
||||||
|
core::either::EitherOutput::Second((remote_id, out)) => {
|
||||||
|
Ok((core::either::EitherOutput::Second(out), remote_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.timeout(Duration::from_secs(20));
|
||||||
|
|
||||||
|
// Multiplexing
|
||||||
|
let transport = transport
|
||||||
|
.and_then(move |(stream, peer_id), endpoint| {
|
||||||
|
let peer_id2 = peer_id.clone();
|
||||||
|
let upgrade = core::upgrade::SelectUpgrade::new(
|
||||||
|
libp2p::yamux::Config::default(),
|
||||||
|
libp2p::mplex::MplexConfig::new(),
|
||||||
|
)
|
||||||
|
.map_inbound(move |muxer| (peer_id, muxer))
|
||||||
|
.map_outbound(move |muxer| (peer_id2, muxer));
|
||||||
|
|
||||||
|
core::upgrade::apply(stream, upgrade, endpoint, core::upgrade::Version::V1)
|
||||||
|
.map(|(id, muxer)| (id, core::muxing::StreamMuxerBox::new(muxer)))
|
||||||
|
})
|
||||||
.timeout(Duration::from_secs(20))
|
.timeout(Duration::from_secs(20))
|
||||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||||
.boxed()
|
.boxed();
|
||||||
|
transport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
/// Events that can be obtained from polling the Libp2p Service.
|
/// Events that can be obtained from polling the Libp2p Service.
|
||||||
pub enum Libp2pEvent {
|
pub enum Libp2pEvent {
|
||||||
/// An RPC response request has been received on the swarm.
|
/// An RPC response request has been received on the swarm.
|
||||||
@ -363,3 +399,13 @@ fn load_private_key(config: &NetworkConfig, log: &slog::Logger) -> Keypair {
|
|||||||
}
|
}
|
||||||
local_private_key
|
local_private_key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate authenticated XX Noise config from identity keys
|
||||||
|
fn generate_noise_config(
|
||||||
|
identity_keypair: &Keypair,
|
||||||
|
) -> noise::NoiseAuthenticated<noise::XX, noise::X25519, ()> {
|
||||||
|
let static_dh_keys = noise::Keypair::<noise::X25519>::new()
|
||||||
|
.into_authentic(identity_keypair)
|
||||||
|
.expect("signing can fail only once during starting a node");
|
||||||
|
noise::NoiseConfig::xx(static_dh_keys).into_authenticated()
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ use eth2_libp2p::NetworkConfig;
|
|||||||
use eth2_libp2p::Service as LibP2PService;
|
use eth2_libp2p::Service as LibP2PService;
|
||||||
use slog::{debug, error, o, Drain};
|
use slog::{debug, error, o, Drain};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use tempdir::TempDir;
|
||||||
|
|
||||||
pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger {
|
pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger {
|
||||||
let decorator = slog_term::TermDecorator::new().build();
|
let decorator = slog_term::TermDecorator::new().build();
|
||||||
@ -24,11 +25,13 @@ pub fn build_config(
|
|||||||
secret_key: Option<String>,
|
secret_key: Option<String>,
|
||||||
) -> NetworkConfig {
|
) -> NetworkConfig {
|
||||||
let mut config = NetworkConfig::default();
|
let mut config = NetworkConfig::default();
|
||||||
|
let path = TempDir::new(&format!("libp2p_test{}", port)).unwrap();
|
||||||
|
|
||||||
config.libp2p_port = port; // tcp port
|
config.libp2p_port = port; // tcp port
|
||||||
config.discovery_port = port; // udp port
|
config.discovery_port = port; // udp port
|
||||||
config.boot_nodes.append(&mut boot_nodes);
|
config.boot_nodes.append(&mut boot_nodes);
|
||||||
config.secret_key_hex = secret_key;
|
config.secret_key_hex = secret_key;
|
||||||
config.network_dir.push(port.to_string());
|
config.network_dir = path.into_path();
|
||||||
// Reduce gossipsub heartbeat parameters
|
// Reduce gossipsub heartbeat parameters
|
||||||
config.gs_config.heartbeat_initial_delay = Duration::from_millis(500);
|
config.gs_config.heartbeat_initial_delay = Duration::from_millis(500);
|
||||||
config.gs_config.heartbeat_interval = Duration::from_millis(500);
|
config.gs_config.heartbeat_interval = Duration::from_millis(500);
|
||||||
@ -43,7 +46,9 @@ pub fn build_libp2p_instance(
|
|||||||
) -> LibP2PService {
|
) -> LibP2PService {
|
||||||
let config = build_config(port, boot_nodes, secret_key);
|
let config = build_config(port, boot_nodes, secret_key);
|
||||||
// launch libp2p service
|
// launch libp2p service
|
||||||
LibP2PService::new(&config, log.clone()).unwrap().1
|
LibP2PService::new(&config, log.clone())
|
||||||
|
.expect("should build libp2p instance")
|
||||||
|
.1
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
168
beacon_node/eth2-libp2p/tests/noise.rs
Normal file
168
beacon_node/eth2-libp2p/tests/noise.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
#![cfg(test)]
|
||||||
|
use crate::behaviour::{Behaviour, BehaviourEvent};
|
||||||
|
use crate::multiaddr::Protocol;
|
||||||
|
use eth2_libp2p::*;
|
||||||
|
use futures::prelude::*;
|
||||||
|
use libp2p::core::identity::Keypair;
|
||||||
|
use libp2p::{
|
||||||
|
core,
|
||||||
|
core::{muxing::StreamMuxerBox, nodes::Substream, transport::boxed::Boxed},
|
||||||
|
secio, PeerId, Swarm, Transport,
|
||||||
|
};
|
||||||
|
use slog::{crit, debug, info, Level};
|
||||||
|
use std::io::{Error, ErrorKind};
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering::Relaxed};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::prelude::*;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
type Libp2pStream = Boxed<(PeerId, StreamMuxerBox), Error>;
|
||||||
|
type Libp2pBehaviour = Behaviour<Substream<StreamMuxerBox>>;
|
||||||
|
|
||||||
|
/// Build and return a eth2_libp2p Swarm with only secio support.
|
||||||
|
fn build_secio_swarm(
|
||||||
|
config: &NetworkConfig,
|
||||||
|
log: slog::Logger,
|
||||||
|
) -> error::Result<Swarm<Libp2pStream, Libp2pBehaviour>> {
|
||||||
|
let local_keypair = Keypair::generate_secp256k1();
|
||||||
|
let local_peer_id = PeerId::from(local_keypair.public());
|
||||||
|
|
||||||
|
let network_globals = Arc::new(NetworkGlobals::new(local_peer_id.clone()));
|
||||||
|
|
||||||
|
let mut swarm = {
|
||||||
|
// Set up the transport - tcp/ws with secio and mplex/yamux
|
||||||
|
let transport = build_secio_transport(local_keypair.clone());
|
||||||
|
// Lighthouse network behaviour
|
||||||
|
let behaviour = Behaviour::new(&local_keypair, config, network_globals.clone(), &log)?;
|
||||||
|
Swarm::new(transport, behaviour, local_peer_id.clone())
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 established"; "address" => format!("{}", log_address));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
crit!(
|
||||||
|
log,
|
||||||
|
"Unable to listen on libp2p address";
|
||||||
|
"error" => format!("{:?}", err),
|
||||||
|
"listen_multiaddr" => format!("{}", listen_multiaddr),
|
||||||
|
);
|
||||||
|
return Err("Libp2p was unable to listen on the given listen address.".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// helper closure for dialing peers
|
||||||
|
let mut dial_addr = |multiaddr: &Multiaddr| {
|
||||||
|
match Swarm::dial_addr(&mut swarm, multiaddr.clone()) {
|
||||||
|
Ok(()) => debug!(log, "Dialing libp2p peer"; "address" => format!("{}", multiaddr)),
|
||||||
|
Err(err) => debug!(
|
||||||
|
log,
|
||||||
|
"Could not connect to peer"; "address" => format!("{}", multiaddr), "error" => format!("{:?}", err)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// attempt to connect to any specified boot-nodes
|
||||||
|
for bootnode_enr in &config.boot_nodes {
|
||||||
|
for multiaddr in &bootnode_enr.multiaddr() {
|
||||||
|
// ignore udp multiaddr if it exists
|
||||||
|
let components = multiaddr.iter().collect::<Vec<_>>();
|
||||||
|
if let Protocol::Udp(_) = components[1] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dial_addr(multiaddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(swarm)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a simple TCP transport with secio, mplex/yamux.
|
||||||
|
fn build_secio_transport(local_private_key: Keypair) -> Boxed<(PeerId, StreamMuxerBox), Error> {
|
||||||
|
let transport = libp2p::tcp::TcpConfig::new().nodelay(true);
|
||||||
|
transport
|
||||||
|
.upgrade(core::upgrade::Version::V1)
|
||||||
|
.authenticate(secio::SecioConfig::new(local_private_key))
|
||||||
|
.multiplex(core::upgrade::SelectUpgrade::new(
|
||||||
|
libp2p::yamux::Config::default(),
|
||||||
|
libp2p::mplex::MplexConfig::new(),
|
||||||
|
))
|
||||||
|
.map(|(peer, muxer), _| (peer, core::muxing::StreamMuxerBox::new(muxer)))
|
||||||
|
.timeout(Duration::from_secs(20))
|
||||||
|
.timeout(Duration::from_secs(20))
|
||||||
|
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if the encryption falls back to secio if noise isn't available
|
||||||
|
#[test]
|
||||||
|
fn test_secio_noise_fallback() {
|
||||||
|
// set up the logging. The level and enabled logging or not
|
||||||
|
let log_level = Level::Trace;
|
||||||
|
let enable_logging = true;
|
||||||
|
|
||||||
|
let log = common::build_log(log_level, enable_logging);
|
||||||
|
|
||||||
|
let noisy_config = common::build_config(56010, vec![], None);
|
||||||
|
let mut noisy_node = Service::new(&noisy_config, log.clone())
|
||||||
|
.expect("should build a libp2p instance")
|
||||||
|
.1;
|
||||||
|
|
||||||
|
let secio_config = common::build_config(56011, vec![common::get_enr(&noisy_node)], None);
|
||||||
|
|
||||||
|
// Building a custom Libp2pService from outside the crate isn't possible because of
|
||||||
|
// private fields in the Libp2pService struct. A swarm is good enough for testing
|
||||||
|
// compatibility with secio.
|
||||||
|
let mut secio_swarm =
|
||||||
|
build_secio_swarm(&secio_config, log.clone()).expect("should build a secio swarm");
|
||||||
|
|
||||||
|
let secio_log = log.clone();
|
||||||
|
|
||||||
|
let noisy_future = future::poll_fn(move || -> Poll<bool, ()> {
|
||||||
|
loop {
|
||||||
|
match noisy_node.poll().unwrap() {
|
||||||
|
_ => return Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let secio_future = future::poll_fn(move || -> Poll<bool, ()> {
|
||||||
|
loop {
|
||||||
|
match secio_swarm.poll().unwrap() {
|
||||||
|
Async::Ready(Some(BehaviourEvent::PeerDialed(peer_id))) => {
|
||||||
|
// secio node negotiated a secio transport with
|
||||||
|
// the noise compatible node
|
||||||
|
info!(secio_log, "Connected to peer {}", peer_id);
|
||||||
|
return Ok(Async::Ready(true));
|
||||||
|
}
|
||||||
|
_ => return Ok(Async::NotReady),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// execute the futures and check the result
|
||||||
|
let test_result = Arc::new(AtomicBool::new(false));
|
||||||
|
let error_result = test_result.clone();
|
||||||
|
let thread_result = test_result.clone();
|
||||||
|
tokio::run(
|
||||||
|
noisy_future
|
||||||
|
.select(secio_future)
|
||||||
|
.timeout(Duration::from_millis(1000))
|
||||||
|
.map_err(move |_| error_result.store(false, Relaxed))
|
||||||
|
.map(move |result| {
|
||||||
|
thread_result.store(result.0, Relaxed);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert!(test_result.load(Relaxed));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user