lighthouse/testing/node_test_rig/src/lib.rs
Divma e190ebb8a0 Support for Ipv6 (#4046)
## Issue Addressed
Add support for ipv6 and dual stack in lighthouse. 

## Proposed Changes
From an user perspective, now setting an ipv6 address, optionally configuring the ports should feel exactly the same as using an ipv4 address. If listening over both ipv4 and ipv6 then the user needs to:
- use the `--listen-address` two times (ipv4 and ipv6 addresses)
- `--port6` becomes then required
- `--discovery-port6` can now be used to additionally configure the ipv6 udp port

### Rough list of code changes
- Discovery:
  - Table filter and ip mode set to match the listening config. 
  - Ipv6 address, tcp port and udp port set in the ENR builder
  - Reported addresses now check which tcp port to give to libp2p
- LH Network Service:
  - Can listen over Ipv6, Ipv4, or both. This uses two sockets. Using mapped addresses is disabled from libp2p and it's the most compatible option.
- NetworkGlobals:
  - No longer stores udp port since was not used at all. Instead, stores the Ipv4 and Ipv6 TCP ports.
- NetworkConfig:
  - Update names to make it clear that previous udp and tcp ports in ENR were Ipv4
  - Add fields to configure Ipv6 udp and tcp ports in the ENR
  - Include advertised enr Ipv6 address.
  - Add type to model Listening address that's either Ipv4, Ipv6 or both. A listening address includes the ip, udp port and tcp port.
- UPnP:
  - Kept only for ipv4
- Cli flags:
  - `--listen-addresses` now can take up to two values
  - `--port` will apply to ipv4 or ipv6 if only one listening address is given. If two listening addresses are given it will apply only to Ipv4.
  - `--port6` New flag required when listening over ipv4 and ipv6 that applies exclusively to Ipv6.
  - `--discovery-port` will now apply to ipv4 and ipv6 if only one listening address is given.
  - `--discovery-port6` New flag to configure the individual udp port of ipv6 if listening over both ipv4 and ipv6.
  - `--enr-udp-port` Updated docs to specify that it only applies to ipv4. This is an old behaviour.
  - `--enr-udp6-port` Added to configure the enr udp6 field.
  - `--enr-tcp-port` Updated docs to specify that it only applies to ipv4. This is an old behaviour.
  - `--enr-tcp6-port` Added to configure the enr tcp6 field.
  - `--enr-addresses` now can take two values.
  - `--enr-match` updated behaviour.
- Common:
  - rename `unused_port` functions to specify that they are over ipv4.
  - add functions to get unused ports over ipv6.
- Testing binaries
  - Updated code to reflect network config changes and unused_port changes.

## Additional Info

TODOs:
- use two sockets in discovery. I'll get back to this and it's on https://github.com/sigp/discv5/pull/160
- lcli allow listening over two sockets in generate_bootnodes_enr
- add at least one smoke flag for ipv6 (I have tested this and works for me)
- update the book
2023-03-14 01:13:34 +00:00

244 lines
8.4 KiB
Rust

//! Provides easy ways to run a beacon node or validator client in-process.
//!
//! Intended to be used for testing and simulation purposes. Not for production.
use beacon_node::ProductionBeaconNode;
use environment::RuntimeContext;
use eth2::{reqwest::ClientBuilder, BeaconNodeHttpClient, Timeouts};
use sensitive_url::SensitiveUrl;
use std::path::PathBuf;
use std::time::Duration;
use std::time::{SystemTime, UNIX_EPOCH};
use tempfile::{Builder as TempBuilder, TempDir};
use types::EthSpec;
use validator_client::ProductionValidatorClient;
use validator_dir::insecure_keys::build_deterministic_validator_dirs;
pub use beacon_node::{ClientConfig, ClientGenesis, ProductionClient};
pub use environment;
pub use eth2;
pub use execution_layer::test_utils::{
Config as MockServerConfig, MockExecutionConfig, MockServer,
};
pub use validator_client::Config as ValidatorConfig;
/// The global timeout for HTTP requests to the beacon node.
const HTTP_TIMEOUT: Duration = Duration::from_secs(4);
/// Provides a beacon node that is running in the current process on a given tokio executor (it
/// is _local_ to this process).
///
/// Intended for use in testing and simulation. Not for production.
pub struct LocalBeaconNode<E: EthSpec> {
pub client: ProductionClient<E>,
pub datadir: TempDir,
}
impl<E: EthSpec> LocalBeaconNode<E> {
/// Starts a new, production beacon node on the tokio runtime in the given `context`.
///
/// The node created is using the same types as the node we use in production.
pub async fn production(
context: RuntimeContext<E>,
mut client_config: ClientConfig,
) -> Result<Self, String> {
// Creates a temporary directory that will be deleted once this `TempDir` is dropped.
let datadir = TempBuilder::new()
.prefix("lighthouse_node_test_rig")
.tempdir()
.expect("should create temp directory for client datadir");
client_config.set_data_dir(datadir.path().into());
client_config.network.network_dir = PathBuf::from(datadir.path()).join("network");
ProductionBeaconNode::new(context, client_config)
.await
.map(move |client| Self {
client: client.into_inner(),
datadir,
})
}
}
impl<E: EthSpec> LocalBeaconNode<E> {
/// Returns a `RemoteBeaconNode` that can connect to `self`. Useful for testing the node as if
/// it were external this process.
pub fn remote_node(&self) -> Result<BeaconNodeHttpClient, String> {
let listen_addr = self
.client
.http_api_listen_addr()
.ok_or("A remote beacon node must have a http server")?;
let beacon_node_url: SensitiveUrl = SensitiveUrl::parse(
format!("http://{}:{}", listen_addr.ip(), listen_addr.port()).as_str(),
)
.map_err(|e| format!("Unable to parse beacon node URL: {:?}", e))?;
let beacon_node_http_client = ClientBuilder::new()
.timeout(HTTP_TIMEOUT)
.build()
.map_err(|e| format!("Unable to build HTTP client: {:?}", e))?;
Ok(BeaconNodeHttpClient::from_components(
beacon_node_url,
beacon_node_http_client,
Timeouts::set_all(HTTP_TIMEOUT),
))
}
}
pub fn testing_client_config() -> ClientConfig {
let mut client_config = ClientConfig::default();
// Setting ports to `0` means that the OS will choose some available port.
client_config
.network
.set_ipv4_listening_address(std::net::Ipv4Addr::UNSPECIFIED, 0, 0);
client_config.network.upnp_enabled = false;
client_config.http_api.enabled = true;
client_config.http_api.listen_port = 0;
client_config.dummy_eth1_backend = true;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("should get system time")
.as_secs();
client_config.genesis = ClientGenesis::Interop {
validator_count: 8,
genesis_time: now,
};
client_config
}
pub fn testing_validator_config() -> ValidatorConfig {
ValidatorConfig {
init_slashing_protection: true,
disable_auto_discover: false,
..ValidatorConfig::default()
}
}
/// Contains the directories for a `LocalValidatorClient`.
///
/// This struct is separate to `LocalValidatorClient` to allow for pre-computation of validator
/// keypairs since the task is quite resource intensive.
pub struct ValidatorFiles {
pub validator_dir: TempDir,
pub secrets_dir: TempDir,
}
impl ValidatorFiles {
/// Creates temporary data and secrets dirs.
pub fn new() -> Result<Self, String> {
let datadir = TempBuilder::new()
.prefix("lighthouse-validator-client")
.tempdir()
.map_err(|e| format!("Unable to create VC data dir: {:?}", e))?;
let secrets_dir = TempBuilder::new()
.prefix("lighthouse-validator-client-secrets")
.tempdir()
.map_err(|e| format!("Unable to create VC secrets dir: {:?}", e))?;
Ok(Self {
validator_dir: datadir,
secrets_dir,
})
}
/// Creates temporary data and secrets dirs, preloaded with keystores.
pub fn with_keystores(keypair_indices: &[usize]) -> Result<Self, String> {
let this = Self::new()?;
build_deterministic_validator_dirs(
this.validator_dir.path().into(),
this.secrets_dir.path().into(),
keypair_indices,
)
.map_err(|e| format!("Unable to build validator directories: {:?}", e))?;
Ok(this)
}
}
/// Provides a validator client that is running in the current process on a given tokio executor (it
/// is _local_ to this process).
///
/// Intended for use in testing and simulation. Not for production.
pub struct LocalValidatorClient<T: EthSpec> {
pub client: ProductionValidatorClient<T>,
pub files: ValidatorFiles,
}
impl<E: EthSpec> LocalValidatorClient<E> {
/// Creates a validator client with insecure deterministic keypairs. The validator directories
/// are created in a temp dir then removed when the process exits.
///
/// The validator created is using the same types as the node we use in production.
pub async fn production_with_insecure_keypairs(
context: RuntimeContext<E>,
config: ValidatorConfig,
files: ValidatorFiles,
) -> Result<Self, String> {
Self::new(context, config, files).await
}
/// Creates a validator client that attempts to read keys from the default data dir.
///
/// - The validator created is using the same types as the node we use in production.
/// - It is recommended to use `production_with_insecure_keypairs` for testing.
pub async fn production(
context: RuntimeContext<E>,
config: ValidatorConfig,
) -> Result<Self, String> {
let files = ValidatorFiles::new()?;
Self::new(context, config, files).await
}
async fn new(
context: RuntimeContext<E>,
mut config: ValidatorConfig,
files: ValidatorFiles,
) -> Result<Self, String> {
config.validator_dir = files.validator_dir.path().into();
config.secrets_dir = files.secrets_dir.path().into();
ProductionValidatorClient::new(context, config)
.await
.map(move |mut client| {
client
.start_service()
.expect("should start validator services");
Self { client, files }
})
}
}
/// Provides an execution engine api server that is running in the current process on a given tokio executor (it
/// is _local_ to this process).
///
/// Intended for use in testing and simulation. Not for production.
pub struct LocalExecutionNode<E: EthSpec> {
pub server: MockServer<E>,
pub datadir: TempDir,
}
impl<E: EthSpec> LocalExecutionNode<E> {
pub fn new(context: RuntimeContext<E>, config: MockExecutionConfig) -> Self {
let datadir = TempBuilder::new()
.prefix("lighthouse_node_test_rig_el")
.tempdir()
.expect("should create temp directory for client datadir");
let jwt_file_path = datadir.path().join("jwt.hex");
if let Err(e) = std::fs::write(jwt_file_path, config.jwt_key.hex_string()) {
panic!("Failed to write jwt file {}", e);
}
Self {
server: MockServer::new_with_config(&context.executor.handle().unwrap(), config),
datadir,
}
}
}