diff --git a/core/src/cluster_info.rs b/core/src/cluster_info.rs index 62a18de9a5..a7ada5291b 100644 --- a/core/src/cluster_info.rs +++ b/core/src/cluster_info.rs @@ -3358,6 +3358,7 @@ impl Node { }, } } + fn get_gossip_port( gossip_addr: &SocketAddr, port_range: PortRange, @@ -3378,6 +3379,60 @@ impl Node { bind_in_range(bind_ip_addr, port_range).expect("Failed to bind") } + pub fn new_single_bind( + pubkey: &Pubkey, + gossip_addr: &SocketAddr, + port_range: PortRange, + bind_ip_addr: IpAddr, + ) -> Self { + let (gossip_port, (gossip, ip_echo)) = + Self::get_gossip_port(gossip_addr, port_range, bind_ip_addr); + let (tvu_port, tvu) = Self::bind(bind_ip_addr, port_range); + let (tvu_forwards_port, tvu_forwards) = Self::bind(bind_ip_addr, port_range); + let (tpu_port, tpu) = Self::bind(bind_ip_addr, port_range); + let (tpu_forwards_port, tpu_forwards) = Self::bind(bind_ip_addr, port_range); + let (_, retransmit_socket) = Self::bind(bind_ip_addr, port_range); + let (repair_port, repair) = Self::bind(bind_ip_addr, port_range); + let (serve_repair_port, serve_repair) = Self::bind(bind_ip_addr, port_range); + let (_, broadcast) = Self::bind(bind_ip_addr, port_range); + + let rpc_port = find_available_port_in_range(bind_ip_addr, port_range).unwrap(); + let rpc_pubsub_port = find_available_port_in_range(bind_ip_addr, port_range).unwrap(); + + let info = ContactInfo { + id: *pubkey, + gossip: SocketAddr::new(gossip_addr.ip(), gossip_port), + tvu: SocketAddr::new(gossip_addr.ip(), tvu_port), + tvu_forwards: SocketAddr::new(gossip_addr.ip(), tvu_forwards_port), + repair: SocketAddr::new(gossip_addr.ip(), repair_port), + tpu: SocketAddr::new(gossip_addr.ip(), tpu_port), + tpu_forwards: SocketAddr::new(gossip_addr.ip(), tpu_forwards_port), + unused: socketaddr_any!(), + rpc: SocketAddr::new(gossip_addr.ip(), rpc_port), + rpc_pubsub: SocketAddr::new(gossip_addr.ip(), rpc_pubsub_port), + serve_repair: SocketAddr::new(gossip_addr.ip(), serve_repair_port), + wallclock: timestamp(), + shred_version: 0, + }; + trace!("new ContactInfo: {:?}", info); + + Node { + info, + sockets: Sockets { + gossip, + ip_echo: Some(ip_echo), + tvu: vec![tvu], + tvu_forwards: vec![tvu_forwards], + tpu: vec![tpu], + tpu_forwards: vec![tpu_forwards], + broadcast: vec![broadcast], + repair, + retransmit_sockets: vec![retransmit_socket], + serve_repair, + }, + } + } + pub fn new_with_external_ip( pubkey: &Pubkey, gossip_addr: &SocketAddr, diff --git a/core/src/test_validator.rs b/core/src/test_validator.rs index bda4401b69..ae217f0b35 100644 --- a/core/src/test_validator.rs +++ b/core/src/test_validator.rs @@ -7,6 +7,7 @@ use { }, solana_client::rpc_client::RpcClient, solana_ledger::{blockstore::create_new_ledger, create_new_tmp_ledger}, + solana_net_utils::PortRange, solana_runtime::{ bank_forks::{ArchiveFormat, SnapshotConfig, SnapshotVersion}, genesis_utils::create_genesis_config_with_leader_ex, @@ -42,6 +43,29 @@ pub struct ProgramInfo { pub program_path: PathBuf, } +#[derive(Debug)] +pub struct TestValidatorNodeConfig { + gossip_addr: SocketAddr, + port_range: PortRange, + bind_ip_addr: IpAddr, +} + +impl Default for TestValidatorNodeConfig { + fn default() -> Self { + const MIN_PORT_RANGE: u16 = 1024; + const MAX_PORT_RANGE: u16 = 65535; + + let bind_ip_addr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); + let port_range = (MIN_PORT_RANGE, MAX_PORT_RANGE); + + Self { + gossip_addr: socketaddr!("127.0.0.1:0"), + port_range, + bind_ip_addr, + } + } +} + #[derive(Default)] pub struct TestValidatorGenesis { fee_rate_governor: FeeRateGovernor, @@ -54,6 +78,7 @@ pub struct TestValidatorGenesis { accounts: HashMap, programs: Vec, epoch_schedule: Option, + node_config: TestValidatorNodeConfig, pub validator_exit: Arc>, pub start_progress: Arc>, pub authorized_voter_keypairs: Arc>>>, @@ -110,6 +135,26 @@ impl TestValidatorGenesis { self } + pub fn gossip_host(&mut self, gossip_host: IpAddr) -> &mut Self { + self.node_config.gossip_addr.set_ip(gossip_host); + self + } + + pub fn gossip_port(&mut self, gossip_port: u16) -> &mut Self { + self.node_config.gossip_addr.set_port(gossip_port); + self + } + + pub fn port_range(&mut self, port_range: PortRange) -> &mut Self { + self.node_config.port_range = port_range; + self + } + + pub fn bind_ip_addr(&mut self, bind_ip_addr: IpAddr) -> &mut Self { + self.node_config.bind_ip_addr = bind_ip_addr; + self + } + /// Add an account to the test environment pub fn add_account(&mut self, address: Pubkey, account: AccountSharedData) -> &mut Self { self.accounts.insert(address, account); @@ -398,7 +443,12 @@ impl TestValidator { .unwrap(), )?; - let mut node = Node::new_localhost_with_pubkey(&validator_identity.pubkey()); + let mut node = Node::new_single_bind( + &validator_identity.pubkey(), + &config.node_config.gossip_addr, + config.node_config.port_range, + config.node_config.bind_ip_addr, + ); if let Some((rpc, rpc_pubsub)) = config.rpc_ports { node.info.rpc = SocketAddr::new(node.info.gossip.ip(), rpc); node.info.rpc_pubsub = SocketAddr::new(node.info.gossip.ip(), rpc_pubsub); diff --git a/validator/src/bin/solana-test-validator.rs b/validator/src/bin/solana-test-validator.rs index 11b42e67e6..9334befbf2 100644 --- a/validator/src/bin/solana-test-validator.rs +++ b/validator/src/bin/solana-test-validator.rs @@ -179,6 +179,44 @@ fn main() { If the ledger already exists then this parameter is silently ignored", ), ) + .arg( + Arg::with_name("gossip_port") + .long("gossip-port") + .value_name("PORT") + .takes_value(true) + .help("Gossip port number for the validator"), + ) + .arg( + Arg::with_name("gossip_host") + .long("gossip-host") + .value_name("HOST") + .takes_value(true) + .validator(solana_net_utils::is_host) + .help( + "Gossip DNS name or IP address for the validator to advertise in gossip \ + [default: 127.0.0.1]", + ), + ) + .arg( + Arg::with_name("dynamic_port_range") + .long("dynamic-port-range") + .value_name("MIN_PORT-MAX_PORT") + .takes_value(true) + .validator(solana_validator::port_range_validator) + .help( + "Range to use for dynamically assigned ports \ + [default: 1024-65535]", + ), + ) + .arg( + Arg::with_name("bind_address") + .long("bind-address") + .value_name("HOST") + .takes_value(true) + .validator(solana_net_utils::is_host) + .default_value("0.0.0.0") + .help("IP address to bind the validator ports [default: 0.0.0.0]"), + ) .arg( Arg::with_name("clone_account") .long("clone") @@ -240,6 +278,25 @@ fn main() { let rpc_port = value_t_or_exit!(matches, "rpc_port", u16); let faucet_port = value_t_or_exit!(matches, "faucet_port", u16); let slots_per_epoch = value_t!(matches, "slots_per_epoch", Slot).ok(); + let gossip_host = matches.value_of("gossip_host").map(|gossip_host| { + solana_net_utils::parse_host(gossip_host).unwrap_or_else(|err| { + eprintln!("Failed to parse --gossip-host: {}", err); + exit(1); + }) + }); + let gossip_port = value_t!(matches, "gossip_port", u16).ok(); + let dynamic_port_range = matches.value_of("dynamic_port_range").map(|port_range| { + solana_net_utils::parse_port_range(port_range).unwrap_or_else(|| { + eprintln!("Failed to parse --dynamic-port-range"); + exit(1); + }) + }); + let bind_address = matches.value_of("bind_address").map(|bind_address| { + solana_net_utils::parse_host(bind_address).unwrap_or_else(|err| { + eprintln!("Failed to parse --bind-address: {}", err); + exit(1); + }) + }); let faucet_addr = Some(SocketAddr::new( IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), @@ -467,6 +524,22 @@ fn main() { )); } + if let Some(gossip_host) = gossip_host { + genesis.gossip_host(gossip_host); + } + + if let Some(gossip_port) = gossip_port { + genesis.gossip_port(gossip_port); + } + + if let Some(dynamic_port_range) = dynamic_port_range { + genesis.port_range(dynamic_port_range); + } + + if let Some(bind_address) = bind_address { + genesis.bind_ip_addr(bind_address); + } + match genesis.start_with_mint_address(mint_address) { Ok(test_validator) => { if let Some(dashboard) = dashboard { diff --git a/validator/src/lib.rs b/validator/src/lib.rs index 8c6dc5a46a..2051544e6c 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -1,5 +1,5 @@ #![allow(clippy::integer_arithmetic)] -pub use solana_core::test_validator; +pub use solana_core::{cluster_info::MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, test_validator}; use { console::style, indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}, @@ -75,6 +75,22 @@ pub fn port_validator(port: String) -> Result<(), String> { .map_err(|e| format!("{:?}", e)) } +pub fn port_range_validator(port_range: String) -> Result<(), String> { + if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) { + if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH { + Err(format!( + "Port range is too small. Try --dynamic-port-range {}-{}", + start, + start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH + )) + } else { + Ok(()) + } + } else { + Err("Invalid port range".to_string()) + } +} + /// Creates a new process bar for processing that will take an unknown amount of time pub fn new_spinner_progress_bar() -> ProgressBar { let progress_bar = ProgressBar::new(42); diff --git a/validator/src/main.rs b/validator/src/main.rs index b56ecf1ff9..12ffca26c1 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -21,9 +21,7 @@ use { DEFAULT_MAX_LEDGER_SHREDS, DEFAULT_MIN_MAX_LEDGER_SHREDS, }, solana_core::{ - cluster_info::{ - ClusterInfo, Node, MINIMUM_VALIDATOR_PORT_RANGE_WIDTH, VALIDATOR_PORT_RANGE, - }, + cluster_info::{ClusterInfo, Node, VALIDATOR_PORT_RANGE}, contact_info::ContactInfo, gossip_service::GossipService, poh_service, @@ -296,22 +294,6 @@ fn wait_for_restart_window( Ok(()) } -fn port_range_validator(port_range: String) -> Result<(), String> { - if let Some((start, end)) = solana_net_utils::parse_port_range(&port_range) { - if end - start < MINIMUM_VALIDATOR_PORT_RANGE_WIDTH { - Err(format!( - "Port range is too small. Try --dynamic-port-range {}-{}", - start, - start + MINIMUM_VALIDATOR_PORT_RANGE_WIDTH - )) - } else { - Ok(()) - } - } else { - Err("Invalid port range".to_string()) - } -} - fn hash_validator(hash: String) -> Result<(), String> { Hash::from_str(&hash) .map(|_| ()) @@ -1298,7 +1280,7 @@ pub fn main() { .value_name("MIN_PORT-MAX_PORT") .takes_value(true) .default_value(default_dynamic_port_range) - .validator(port_range_validator) + .validator(solana_validator::port_range_validator) .help("Range to use for dynamically assigned ports"), ) .arg(