diff --git a/src/dfx/src/commands/replica.rs b/src/dfx/src/commands/replica.rs index c0e851906d..e84113c2f7 100644 --- a/src/dfx/src/commands/replica.rs +++ b/src/dfx/src/commands/replica.rs @@ -1,110 +1,142 @@ use crate::commands::canister::create_waiter; +use crate::config::dfinity::ConfigDefaultsReplica; use crate::lib::environment::Environment; use crate::lib::error::{DfxError, DfxResult}; use crate::lib::message::UserMessage; -use crate::lib::replica_config::{ReplicaConfig, SchedulerConfig}; +use crate::lib::replica_config::{ + HttpHandlerConfig, ReplicaConfig, SchedulerConfig, StateManagerConfig, +}; use clap::{App, Arg, ArgMatches, SubCommand}; use crossbeam::channel::{Receiver, Sender}; use crossbeam::unbounded; use ic_http_agent::{Agent, AgentConfig}; use indicatif::{ProgressBar, ProgressDrawTarget}; +use std::default::Default; use std::io::{Error, ErrorKind}; use std::path::PathBuf; use std::time::Duration; use sysinfo::{Pid, Process, ProcessExt, Signal}; use tokio::runtime::Runtime; -/// Provide necessary arguments to start the Internet Computer -/// locally. See `exec` for further information. +/// Constructs a sub-command to run the Internet Computer replica. pub fn construct() -> App<'static, 'static> { SubCommand::with_name("replica") .about(UserMessage::Replica.to_str()) + .arg( + Arg::with_name("message-gas-limit") + .help(UserMessage::ReplicaMessageGasLimit.to_str()) + .long("message-gas-limit") + .takes_value(true), + ) .arg( Arg::with_name("port") .help(UserMessage::ReplicaPort.to_str()) .long("port") - .takes_value(true) - .default_value("8080") - .validator(|v| { - v.parse::() - .map_err(|_| "Must pass a valid port number.".to_owned()) - .map(|_| ()) - }), - ) - .arg( - Arg::with_name("message-gas-max") - .help(UserMessage::ReplicaMessageGasMax.to_str()) - .long("message-gas-max") - .takes_value(true) - .default_value("5368709120"), + .takes_value(true), ) .arg( - Arg::with_name("round-gas-max") - .help(UserMessage::ReplicaRoundGasMax.to_str()) - .long("round-gas-max") - .takes_value(true) - .default_value("26843545600"), + Arg::with_name("round-gas-limit") + .help(UserMessage::ReplicaRoundGasLimit.to_str()) + .long("round-gas-limit") + .takes_value(true), ) } +/// Gets the configuration options for the Internet Computer replica. +fn get_config(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult { + let config = get_config_from_file(env); + let port = get_port(&config, args)?; + let mut http_handler: HttpHandlerConfig = Default::default(); + if port == 0 { + let file = env.get_temp_dir().join("config").join("port.txt"); + http_handler.write_port_to = Some(file); + } else { + http_handler.use_port = Some(port); + }; + let message_gas_limit = get_message_gas_limit(&config, args)?; + let round_gas_limit = get_round_gas_limit(&config, args)?; + let scheduler = SchedulerConfig { + exec_gas: Some(message_gas_limit), + round_gas_max: Some(round_gas_limit), + } + .validate()?; + let state_manager = StateManagerConfig { + state_root: env.get_state_dir(), + }; + Ok(ReplicaConfig { + http_handler, + scheduler, + state_manager, + }) +} + +/// Gets the configuration options for the Internet Computer replica as they were specified in the +/// dfx configuration file. +fn get_config_from_file(env: &dyn Environment) -> ConfigDefaultsReplica { + env.get_config().map_or(Default::default(), |config| { + config.get_config().get_defaults().get_replica().to_owned() + }) +} + +/// Gets the port number that the Internet Computer replica listens on. First checks if the port +/// number was specified on the command-line using --port, otherwise checks if the port number was +/// specified in the dfx configuration file, otherise defaults to 8080. +fn get_port(config: &ConfigDefaultsReplica, args: &ArgMatches<'_>) -> DfxResult { + args.value_of("port") + .map(|port| port.parse()) + .unwrap_or_else(|| { + let default = 8080; + Ok(config.port.unwrap_or(default)) + }) + .map_err(|err| DfxError::InvalidArgument(format!("Invalid port number: {}", err))) +} + +/// Gets the maximum amount of gas a single message can consume. First checks if the gas limit was +/// specified on the command-line using --message-gas-limit, otherwise checks if the gas limit was +/// specified in the dfx configuration file, otherise defaults to 5368709120. +fn get_message_gas_limit(config: &ConfigDefaultsReplica, args: &ArgMatches<'_>) -> DfxResult { + args.value_of("message-gas-limit") + .map(|limit| limit.parse()) + .unwrap_or_else(|| { + let default = 5_368_709_120; + Ok(config.message_gas_limit.unwrap_or(default)) + }) + .map_err(|err| DfxError::InvalidArgument(format!("Invalid message gas limit: {}", err))) +} + +/// Gets the maximum amount of gas a single round can consume. First checks if the gas limit was +/// specified on the command-line using --round-gas-limit, otherwise checks if the gas limit was +/// specified in the dfx configuration file, otherise defaults to 26843545600. +fn get_round_gas_limit(config: &ConfigDefaultsReplica, args: &ArgMatches<'_>) -> DfxResult { + args.value_of("round-gas-limit") + .map(|limit| limit.parse()) + .unwrap_or_else(|| { + let default = 26_843_545_600; + Ok(config.round_gas_limit.unwrap_or(default)) + }) + .map_err(|err| DfxError::InvalidArgument(format!("Invalid round gas limit: {}", err))) +} + fn ping_and_wait(frontend_url: &str) -> DfxResult { let mut runtime = Runtime::new().expect("Unable to create a runtime"); - let agent = Agent::new(AgentConfig { url: frontend_url, ..AgentConfig::default() })?; - runtime .block_on(agent.ping(create_waiter())) .map_err(DfxError::from) } -fn get_scheduler(args: &ArgMatches<'_>) -> DfxResult { - // Get mssage gas limit. - let message_gas_max = args - .value_of("message-gas-max") - .expect("default value") - .parse() - .map_err(|err| DfxError::InvalidArgument(format!("Invalid message gas limit: {}", err)))?; - // Get round gas limit. - let round_gas_max = args - .value_of("round-gas-max") - .expect("default value") - .parse() - .map_err(|err| DfxError::InvalidArgument(format!("Invalid round gas limit: {}", err)))?; - // Check message and round gas limits. - if message_gas_max >= round_gas_max { - let err = "Round gas limit must exceed message gas limit.".to_string(); - Err(DfxError::InvalidArgument(err)) - } else { - // Return scheduler configuration. - Ok(SchedulerConfig { - exec_gas: Some(message_gas_max), - round_gas_max: Some(round_gas_max), - }) - } -} - -// TODO(eftychis)/In progress: Rename to replica. /// Start the Internet Computer locally. Spawns a proxy to forward and /// manage browser requests. Responsible for running the network (one /// replica at the moment) and the proxy. pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult { let replica_binary_path = env.get_cache().get_binary_command_path("replica")?; let temp_dir = env.get_temp_dir(); - let state_root = env.get_state_dir(); let pid_file_path = temp_dir.join("pid"); - let port = args - .value_of("port") - .unwrap_or("8080") - .parse::() - .expect("Unreachable. Port should have been validated by clap."); - - let scheduler = get_scheduler(args)?; - // We are doing this here to make sure we can write to the temp // pid file. std::fs::write(&pid_file_path, "")?; @@ -121,10 +153,9 @@ pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult { let (_broadcast_stop, is_killed_replica) = unbounded(); b.set_message("Generating IC local replica configuration."); - let replica_config = ReplicaConfig::new(&state_root) - .with_port(port) - .with_scheduler(scheduler) - .to_toml()?; + let config = get_config(env, args)?; + let port = config.http_handler.use_port.expect("non-random port"); + let toml = config.to_toml()?; // TODO(eftychis): we need a proper manager type when we start // spawning multiple replica processes and registry. @@ -137,7 +168,7 @@ pub fn exec(env: &dyn Environment, args: &ArgMatches<'_>) -> DfxResult { &pid_file_path, is_killed_replica, request_stop, - replica_config, + toml, b, ) } diff --git a/src/dfx/src/config/dfinity.rs b/src/dfx/src/config/dfinity.rs index 64fab87879..879defe934 100644 --- a/src/dfx/src/config/dfinity.rs +++ b/src/dfx/src/config/dfinity.rs @@ -13,6 +13,7 @@ pub const CONFIG_FILE_NAME: &str = "dfx.json"; const EMPTY_CONFIG_DEFAULTS: ConfigDefaults = ConfigDefaults { bootstrap: None, build: None, + replica: None, start: None, }; @@ -24,6 +25,14 @@ const EMPTY_CONFIG_DEFAULTS_BOOTSTRAP: ConfigDefaultsBootstrap = ConfigDefaultsB timeout: None, }; +const EMPTY_CONFIG_DEFAULTS_BUILD: ConfigDefaultsBuild = ConfigDefaultsBuild { output: None }; + +const EMPTY_CONFIG_DEFAULTS_REPLICA: ConfigDefaultsReplica = ConfigDefaultsReplica { + message_gas_limit: None, + port: None, + round_gas_limit: None, +}; + const EMPTY_CONFIG_DEFAULTS_START: ConfigDefaultsStart = ConfigDefaultsStart { address: None, port: None, @@ -31,9 +40,7 @@ const EMPTY_CONFIG_DEFAULTS_START: ConfigDefaultsStart = ConfigDefaultsStart { serve_root: None, }; -const EMPTY_CONFIG_DEFAULTS_BUILD: ConfigDefaultsBuild = ConfigDefaultsBuild { output: None }; - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ConfigCanistersCanister { pub main: Option, pub frontend: Option, @@ -48,7 +55,19 @@ pub struct ConfigDefaultsBootstrap { pub timeout: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ConfigDefaultsBuild { + pub output: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ConfigDefaultsReplica { + pub message_gas_limit: Option, + pub port: Option, + pub round_gas_limit: Option, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ConfigDefaultsStart { pub address: Option, pub nodes: Option, @@ -56,11 +75,6 @@ pub struct ConfigDefaultsStart { pub serve_root: Option, } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ConfigDefaultsBuild { - pub output: Option, -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Profile { // debug is for development only @@ -73,6 +87,7 @@ pub enum Profile { pub struct ConfigDefaults { pub bootstrap: Option, pub build: Option, + pub replica: Option, pub start: Option, } @@ -154,6 +169,12 @@ impl ConfigDefaults { None => &EMPTY_CONFIG_DEFAULTS_BUILD, } } + pub fn get_replica(&self) -> &ConfigDefaultsReplica { + match &self.replica { + Some(x) => &x, + None => &EMPTY_CONFIG_DEFAULTS_REPLICA, + } + } pub fn get_start(&self) -> &ConfigDefaultsStart { match &self.start { Some(x) => &x, diff --git a/src/dfx/src/lib/message.rs b/src/dfx/src/lib/message.rs index f846bdebd3..3d39e6e90d 100644 --- a/src/dfx/src/lib/message.rs +++ b/src/dfx/src/lib/message.rs @@ -76,9 +76,9 @@ user_message!( // dfx replica Replica => "Start a local replica.", - ReplicaMessageGasMax => "Maximum amount of gas a single message can consume.", + ReplicaMessageGasLimit => "Maximum amount of gas a single message can consume.", ReplicaPort => "The port the local replica should listen to.", - ReplicaRoundGasMax => "Maximum amount of gas a single round can consume.", + ReplicaRoundGasLimit => "Maximum amount of gas a single round can consume.", // dfx start CleanState => "Cleans state of current project.", diff --git a/src/dfx/src/lib/replica_config.rs b/src/dfx/src/lib/replica_config.rs index be1ea0f012..9146990142 100644 --- a/src/dfx/src/lib/replica_config.rs +++ b/src/dfx/src/lib/replica_config.rs @@ -1,9 +1,10 @@ use crate::lib::error::{DfxError, DfxResult}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; +use std::default::Default; use std::path::{Path, PathBuf}; -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct HttpHandlerConfig { /// Instructs the HTTP handler to use the specified port pub use_port: Option, @@ -15,18 +16,29 @@ pub struct HttpHandlerConfig { pub write_port_to: Option, } -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SchedulerConfig { pub exec_gas: Option, pub round_gas_max: Option, } -#[derive(Debug, Serialize)] +impl SchedulerConfig { + pub fn validate(self) -> DfxResult { + if self.exec_gas >= self.round_gas_max { + let message = "Round gas limit must exceed message gas limit."; + Err(DfxError::InvalidData(message.to_string())) + } else { + Ok(self) + } + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct StateManagerConfig { pub state_root: PathBuf, } -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ReplicaConfig { pub http_handler: HttpHandlerConfig, pub scheduler: SchedulerConfig, @@ -50,6 +62,7 @@ impl ReplicaConfig { } } + #[allow(dead_code)] pub fn with_port(&mut self, port: u16) -> &mut Self { self.http_handler.use_port = Some(port); self.http_handler.write_port_to = None; @@ -62,11 +75,6 @@ impl ReplicaConfig { self } - pub fn with_scheduler(&mut self, scheduler: SchedulerConfig) -> &mut Self { - self.scheduler = scheduler; - self - } - pub fn to_toml(&self) -> DfxResult { toml::to_string(&self).map_err(DfxError::CouldNotSerializeClientConfiguration) }