diff --git a/crates/sui/src/sui_commands.rs b/crates/sui/src/sui_commands.rs index 6e9f7b4fc51e1..b59ca8a50ad86 100644 --- a/crates/sui/src/sui_commands.rs +++ b/crates/sui/src/sui_commands.rs @@ -14,7 +14,7 @@ use move_analyzer::analyzer; use move_package::BuildConfig; use rand::rngs::OsRng; use std::io::{stderr, stdout, Write}; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr}; use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -59,35 +59,43 @@ const CONCURRENCY_LIMIT: usize = 30; const DEFAULT_EPOCH_DURATION_MS: u64 = 60_000; const DEFAULT_FAUCET_NUM_COINS: usize = 5; // 5 coins per request was the default in sui-test-validator const DEFAULT_FAUCET_MIST_AMOUNT: u64 = 200_000_000_000; // 200 SUI +const DEFAULT_FAUCET_PORT: u16 = 9123; +#[cfg(feature = "indexer")] +const DEFAULT_GRAPHQL_PORT: u16 = 9125; +#[cfg(feature = "indexer")] +const DEFAULT_INDEXER_PORT: u16 = 9124; #[cfg(feature = "indexer")] #[derive(Args)] pub struct IndexerFeatureArgs { - /// Start an indexer with default host and port: 0.0.0.0:9124, or on the port provided. + /// Start an indexer with default host and port: 0.0.0.0:9124. This flag accepts also a port, + /// a host, or both (e.g., 0.0.0.0:9124). /// When providing a specific value, please use the = sign between the flag and value: - /// `--with-indexer=6124`. + /// `--with-indexer=6124` or `--with-indexer=0.0.0.0`, or `--with-indexer=0.0.0.0:9124` /// The indexer will be started in writer mode and reader mode. #[clap(long, - default_missing_value = "9124", + default_value = "0.0.0.0:9124", + default_missing_value = "0.0.0.0:9124", num_args = 0..=1, require_equals = true, - value_name = "INDEXER_PORT" + value_name = "INDEXER_HOST_PORT", )] - with_indexer: Option, + with_indexer: Option, - /// Start a GraphQL server on localhost and port: 127.0.0.1:9125, or on the port provided. + /// Start a GraphQL server with default host and port: 0.0.0.0:9125. This flag accepts also a + /// port, a host, or both (e.g., 0.0.0.0:9125). /// When providing a specific value, please use the = sign between the flag and value: - /// `--with-graphql=6125`. + /// `--with-graphql=6124` or `--with-graphql=0.0.0.0`, or `--with-graphql=0.0.0.0:9125` /// Note that GraphQL requires a running indexer, which will be enabled by default if the /// `--with-indexer` flag is not set. #[clap( long, - default_missing_value = "9125", + default_missing_value = "0.0.0.0:9125", num_args = 0..=1, require_equals = true, - value_name = "GRAPHQL_PORT" + value_name = "GRAPHQL_HOST_PORT" )] - with_graphql: Option, + with_graphql: Option, /// Port for the Indexer Postgres DB. Default port is 5432. #[clap(long, default_value = "5432")] @@ -156,17 +164,18 @@ pub enum SuiCommand { #[clap(long)] force_regenesis: bool, - /// Start a faucet with default host and port: 127.0.0.1:9123, or on the port provided. + /// Start a faucet with default host and port: 0.0.0.0:9123. This flag accepts also a + /// port, a host, or both (e.g., 0.0.0.0:9123). /// When providing a specific value, please use the = sign between the flag and value: - /// `--with-faucet=6123`. + /// `--with-faucet=6124` or `--with-faucet=0.0.0.0`, or `--with-faucet=0.0.0.0:9123` #[clap( long, - default_missing_value = "9123", + default_missing_value = "0.0.0.0:9123", num_args = 0..=1, require_equals = true, - value_name = "FAUCET_PORT" + value_name = "FAUCET_HOST_PORT", )] - with_faucet: Option, + with_faucet: Option, #[cfg(feature = "indexer")] #[clap(flatten)] @@ -533,7 +542,7 @@ impl SuiCommand { /// Starts a local network with the given configuration. async fn start( config: Option, - with_faucet: Option, + with_faucet: Option, #[cfg(feature = "indexer")] indexer_feature_args: IndexerFeatureArgs, force_regenesis: bool, epoch_duration_ms: Option, @@ -579,18 +588,6 @@ async fn start( ); } - #[cfg(feature = "indexer")] - if let Some(indexer_rpc_port) = with_indexer { - tracing::info!("Starting the indexer service at 0.0.0.0:{indexer_rpc_port}"); - } - #[cfg(feature = "indexer")] - if let Some(graphql_port) = with_graphql { - tracing::info!("Starting the GraphQL service at 127.0.0.1:{graphql_port}"); - } - if let Some(faucet_port) = with_faucet { - tracing::info!("Starting the faucet service at 127.0.0.1:{faucet_port}"); - } - let mut swarm_builder = Swarm::builder(); // If this is set, then no data will be persisted between runs, and a new genesis will be // generated each run. @@ -675,8 +672,10 @@ async fn start( let pg_address = format!("postgres://{pg_user}:{pg_password}@{pg_host}:{pg_port}/{pg_db_name}"); #[cfg(feature = "indexer")] - if with_indexer.is_some() { - let indexer_address = format!("0.0.0.0:{}", with_indexer.unwrap_or_default()); + if let Some(input) = with_indexer { + let indexer_address = parse_host_port(input, DEFAULT_INDEXER_PORT) + .map_err(|_| anyhow!("Invalid indexer host and port"))?; + tracing::info!("Starting the indexer service at {indexer_address}"); // Start in writer mode start_test_indexer::( Some(pg_address.clone()), @@ -699,10 +698,13 @@ async fn start( } #[cfg(feature = "indexer")] - if with_graphql.is_some() { + if let Some(input) = with_graphql { + let graphql_address = parse_host_port(input, DEFAULT_GRAPHQL_PORT) + .map_err(|_| anyhow!("Invalid graphql host and port"))?; + tracing::info!("Starting the GraphQL service at {graphql_address}"); let graphql_connection_config = ConnectionConfig::new( - Some(with_graphql.unwrap_or_default()), - None, + Some(graphql_address.port()), + Some(graphql_address.ip().to_string()), Some(pg_address), None, None, @@ -716,7 +718,11 @@ async fn start( .await; info!("GraphQL started"); } - if with_faucet.is_some() { + + if let Some(input) = with_faucet { + let faucet_address = parse_host_port(input, DEFAULT_FAUCET_PORT) + .map_err(|_| anyhow!("Invalid faucet host and port"))?; + tracing::info!("Starting the faucet service at {faucet_address}"); let config_dir = if force_regenesis { tempdir()?.into_path() } else { @@ -726,12 +732,19 @@ async fn start( } }; + let host_ip = match faucet_address { + SocketAddr::V4(addr) => *addr.ip(), + _ => bail!("Faucet configuration requires an IPv4 address"), + }; + let config = FaucetConfig { - port: with_faucet.unwrap_or_default(), + host_ip, + port: faucet_address.port(), num_coins: DEFAULT_FAUCET_NUM_COINS, amount: DEFAULT_FAUCET_MIST_AMOUNT, ..Default::default() }; + let prometheus_registry = prometheus::Registry::new(); if force_regenesis { let kp = swarm.config_mut().account_keys.swap_remove(0); @@ -1142,3 +1155,26 @@ fn read_line() -> Result { io::stdin().read_line(&mut s)?; Ok(s.trim_end().to_string()) } + +/// Parse the input string into a SocketAddr, with a default port if none is provided. +pub fn parse_host_port( + input: String, + default_port_if_missing: u16, +) -> Result { + let default_host = "0.0.0.0"; + let mut input = input; + if input.contains("localhost") { + input = input.replace("localhost", "127.0.0.1"); + } + if input.contains(':') { + input.parse::() + } else if input.contains('.') { + format!("{input}:{default_port_if_missing}").parse::() + } else if input.is_empty() { + format!("{default_host}:{default_port_if_missing}").parse::() + } else if !input.is_empty() { + format!("{default_host}:{input}").parse::() + } else { + format!("{default_host}:{default_port_if_missing}").parse::() + } +} diff --git a/crates/sui/tests/cli_tests.rs b/crates/sui/tests/cli_tests.rs index ca19cd5231545..59a13bc6e4c7e 100644 --- a/crates/sui/tests/cli_tests.rs +++ b/crates/sui/tests/cli_tests.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use std::io::Read; +use std::net::SocketAddr; use std::os::unix::prelude::FileExt; use std::{fmt::Write, fs::read_dir, path::PathBuf, str, thread, time::Duration}; @@ -31,7 +32,7 @@ use sui::{ estimate_gas_budget, Opts, OptsWithGas, SuiClientCommandResult, SuiClientCommands, SwitchResponse, }, - sui_commands::SuiCommand, + sui_commands::{parse_host_port, SuiCommand}, }; use sui_config::{ PersistedConfig, SUI_CLIENT_CONFIG, SUI_FULLNODE_CONFIG, SUI_GENESIS_FILENAME, @@ -3932,3 +3933,36 @@ async fn test_clever_errors() -> Result<(), anyhow::Error> { insta::assert_snapshot!(error_string); Ok(()) } + +#[tokio::test] +async fn test_parse_host_port() { + let input = "127.0.0.0"; + let result = parse_host_port(input.to_string(), 9123).unwrap(); + assert_eq!(result, "127.0.0.0:9123".parse::().unwrap()); + + let input = "127.0.0.5:9124"; + let result = parse_host_port(input.to_string(), 9123).unwrap(); + assert_eq!(result, "127.0.0.5:9124".parse::().unwrap()); + + let input = "9090"; + let result = parse_host_port(input.to_string(), 9123).unwrap(); + assert_eq!(result, "0.0.0.0:9090".parse::().unwrap()); + + let input = ""; + let result = parse_host_port(input.to_string(), 9123).unwrap(); + assert_eq!(result, "0.0.0.0:9123".parse::().unwrap()); + + let result = parse_host_port("localhost".to_string(), 9899).unwrap(); + assert_eq!(result, "127.0.0.1:9899".parse::().unwrap()); + + let input = "asg"; + assert!(parse_host_port(input.to_string(), 9123).is_err()); + let input = "127.0.0:900"; + assert!(parse_host_port(input.to_string(), 9123).is_err()); + let input = "127.0.0"; + assert!(parse_host_port(input.to_string(), 9123).is_err()); + let input = "127."; + assert!(parse_host_port(input.to_string(), 9123).is_err()); + let input = "127.9.0.1:asb"; + assert!(parse_host_port(input.to_string(), 9123).is_err()); +}