Skip to content

Commit

Permalink
[CLI] Make service hosts configurable in sui start (#18607)
Browse files Browse the repository at this point in the history
## Description 

Make indexer, GraphQL, faucet hosts configurable. When using Docker, due
to the way it sets up a network interface for the container, the
services need to bind to 0.0.0.0 to be able to be accessed from outside
the container. This PR enables configurable hosts for these three
services via optional flags:
- `--indexer-host 0.0.0.0`
- `--faucet-host 0.0.0.0`
- `--graphql-host 0.0.0.0`

If no host flag is provided, it will use the default `0.0.0.0` one.

In addition, I found a bug where if the `default_value` is not provided,
calling `unwrap_or_default` will return value 0 (if that field is an
int). For example, if we call `--with-graphql`, the indexer port would
have been set to 0 because the `default_missing_value` is only set when
the flag is passed, but not when it is not passed, which is the case
here due `with-indexer` being implicit enabled.

This supersedes #14701 and should close #14701.

## Test plan 

How did you test the new or updated feature?

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] Indexer: 
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [x] CLI: Changed `sui start` to allow configurable hosts for indexer
(`--indexer-host `), GraphQL (`--graphql-host `), and faucet
(`--faucet-host`) services. This will enable to use `sui start` from a
Docker container. By default, all services start with `0.0.0.0` host.
- [ ] Rust SDK:
  • Loading branch information
stefan-mysten authored Jul 12, 2024
1 parent 3c9b596 commit 6b425c9
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 37 deletions.
108 changes: 72 additions & 36 deletions crates/sui/src/sui_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u16>,
with_indexer: Option<String>,

/// 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<u16>,
with_graphql: Option<String>,

/// Port for the Indexer Postgres DB. Default port is 5432.
#[clap(long, default_value = "5432")]
Expand Down Expand Up @@ -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<u16>,
with_faucet: Option<String>,

#[cfg(feature = "indexer")]
#[clap(flatten)]
Expand Down Expand Up @@ -533,7 +542,7 @@ impl SuiCommand {
/// Starts a local network with the given configuration.
async fn start(
config: Option<PathBuf>,
with_faucet: Option<u16>,
with_faucet: Option<String>,
#[cfg(feature = "indexer")] indexer_feature_args: IndexerFeatureArgs,
force_regenesis: bool,
epoch_duration_ms: Option<u64>,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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::<diesel::PgConnection>(
Some(pg_address.clone()),
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -1142,3 +1155,26 @@ fn read_line() -> Result<String, anyhow::Error> {
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<SocketAddr, AddrParseError> {
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::<SocketAddr>()
} else if input.contains('.') {
format!("{input}:{default_port_if_missing}").parse::<SocketAddr>()
} else if input.is_empty() {
format!("{default_host}:{default_port_if_missing}").parse::<SocketAddr>()
} else if !input.is_empty() {
format!("{default_host}:{input}").parse::<SocketAddr>()
} else {
format!("{default_host}:{default_port_if_missing}").parse::<SocketAddr>()
}
}
36 changes: 35 additions & 1 deletion crates/sui/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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::<SocketAddr>().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::<SocketAddr>().unwrap());

let input = "9090";
let result = parse_host_port(input.to_string(), 9123).unwrap();
assert_eq!(result, "0.0.0.0:9090".parse::<SocketAddr>().unwrap());

let input = "";
let result = parse_host_port(input.to_string(), 9123).unwrap();
assert_eq!(result, "0.0.0.0:9123".parse::<SocketAddr>().unwrap());

let result = parse_host_port("localhost".to_string(), 9899).unwrap();
assert_eq!(result, "127.0.0.1:9899".parse::<SocketAddr>().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());
}

0 comments on commit 6b425c9

Please sign in to comment.