diff --git a/config/src/comments.rs b/config/src/comments.rs new file mode 100644 index 000000000..8050fd5f6 --- /dev/null +++ b/config/src/comments.rs @@ -0,0 +1,469 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Comments for configuration + injection into output .toml +use std::collections::HashMap; + +/// maps entries to Comments that should preceed them +fn comments() -> HashMap { + let mut retval = HashMap::new(); + retval.insert( + "[server]".to_string(), + " +# Sample Server Configuration File for Grin +# +# When running the grin executable without specifying any command line +# arguments, it will look for this file in three places, in the following +# order: +# +# -The working directory +# -The directory in which the executable resides +# -[user home]/.grin +# + +######################################### +### SERVER CONFIGURATION ### +######################################### + +#Server connection details +" + .to_string(), + ); + + retval.insert( + "api_http_addr".to_string(), + " +#the address on which services will listen, e.g. Transaction Pool +" + .to_string(), + ); + + retval.insert( + "db_root".to_string(), + " +#the directory, relative to current, in which the grin blockchain +#is stored +" + .to_string(), + ); + + retval.insert( + "chain_type".to_string(), + " +#The chain type, which defines the genesis block and the set of cuckoo +#parameters used for mining. Can be: +#AutomatedTesting - For CI builds and instant blockchain creation +#UserTesting - For regular user testing (cuckoo 16) +#Testnet1 - Testnet1 genesis block (cuckoo 16) +#Testnet2 - Testnet2 genesis block (cuckoo 30) +" + .to_string(), + ); + + retval.insert( + "chain_validation_mode".to_string(), + " +#The chain validation mode, defines how often (if at all) we +#want to run a full chain validation. Can be: +#\"EveryBlock\" - run full chain validation when processing each block (except during sync) +#\"Disabled\" - disable full chain validation (just run regular block validation) +" + .to_string(), + ); + + retval.insert( + "archive_mode".to_string(), + " +#run the node in \"full archive\" mode (default is fast-sync, pruned node) +" + .to_string(), + ); + + retval.insert( + "skip_sync_wait".to_string(), + " +#skip waiting for sync on startup, (optional param, mostly for testing) +" + .to_string(), + ); + + retval.insert( + "run_tui".to_string(), + " +#whether to run the ncurses TUI. Ncurses must be installed and this +#will also disable logging to stdout +" + .to_string(), + ); + + retval.insert( + "run_wallet_listener".to_string(), + " +#Whether to run the wallet listener with the server by default +" + .to_string(), + ); + + retval.insert( + "run_wallet_owner_api".to_string(), + " +# Whether to run the web-wallet API (will only run on localhost) +# grin wallet web will run this automatically, so this should +# only be set to true for test/development purposes +" + .to_string(), + ); + + retval.insert( + "run_test_miner".to_string(), + " +#Whether to run a test miner. This is only for developer testing (chaintype +#usertesting) at cuckoo 16, and will only mine into the default wallet port. +#real mining should use the standalone grin-miner +" + .to_string(), + ); + + retval.insert( + "[server.dandelion_config]".to_string(), + " +######################################### +### DANDELION CONFIGURATION ### +######################################### +" + .to_string(), + ); + + retval.insert( + "relay_secs".to_string(), + " +#dandelion relay time (choose new relay peer every n secs) +" + .to_string(), + ); + + retval.insert( + "embargo_secs".to_string(), + " +#fluff and broadcast after embargo expires if tx not seen on network +" + .to_string(), + ); + + retval.insert( + "patience_secs".to_string(), + " +#run dandelion stem/fluff processing every n secs (stem tx aggregation in this window) +" + .to_string(), + ); + retval.insert( + "stem_probability".to_string(), + " +#dandelion stem probability (stem 90% of the time, fluff 10% of the time) +" + .to_string(), + ); + + retval.insert( + "[server.p2p_config]".to_string(), + "#test miner wallet URL (burns if this doesn't exist) +#test_miner_wallet_url = \"http://127.0.0.1:13415\" + +######################################### +### SERVER P2P CONFIGURATION ### +######################################### +#The P2P server details (i.e. the server that communicates with other +" + .to_string(), + ); + + retval.insert( + "host".to_string(), + " +#The interface on which to listen. +#0.0.0.0 will listen on all interfaces, alowing others to interact +#127.0.0.1 will listen on the local machine only +" + .to_string(), + ); + + retval.insert( + "port".to_string(), + " +#The port on which to listen. +" + .to_string(), + ); + + retval.insert( + "seeding_type".to_string(), + " +#How to seed this server, can be None, List, WebStatic or DNSSeed +#If the seeding type is List, the list of peers to connect to can +#be specified as follows: +#seeds = [\"192.168.0.1:13414\",\"192.168.0.2:13414\"] +" + .to_string(), + ); + + retval.insert( + "seeds".to_string(), + " +#If seeding_type = List, the list of peers to connect to. +" + .to_string(), + ); + + retval.insert( + "[server.p2p_config.capabilities]".to_string(), + "#7 = Bit flags for FULL_NODE, this structure needs to be changed +#internally to make it more configurable +" + .to_string(), + ); + + retval.insert( + "[server.pool_config]".to_string(), + "#hardcoded peer lists for allow/deny +#will *only* connect to peers in allow list +#peers_allow = [\"192.168.0.1:13414\", \"192.168.0.2:13414\"] +#will *never* connect to peers in deny list +#peers_deny = [\"192.168.0.3:13414\", \"192.168.0.4:13414\"] +#a list of preferred peers to connect to +#peers_preferred = [\"192.168.0.1:13414\",\"192.168.0.2:13414\"] + +#how long a banned peer should stay banned +#ban_window = 10800 + +#maximum number of peers +#peer_max_count = 25 + +#preferred minimum number of peers (we'll actively keep trying to add peers +#until we get to at least this number +#peer_min_preferred_count = 8 + +######################################### +### MEMPOOL CONFIGURATION ### +######################################### +" + .to_string(), + ); + + retval.insert( + "accept_fee_base".to_string(), + " +#Base fee that's accepted into the pool +" + .to_string(), + ); + + retval.insert( + "max_pool_size".to_string(), + " +#Maximum number of transactions allowed in the pool +" + .to_string(), + ); + + retval.insert( + "[server.stratum_mining_config]".to_string(), + " +################################################ +### STRATUM MINING SERVER CONFIGURATION ### +################################################ +" + .to_string(), + ); + + retval.insert( + "enable_stratum_server".to_string(), + " +#whether stratum server is enabled +" + .to_string(), + ); + + retval.insert( + "stratum_server_addr".to_string(), + " +#what port and address for the stratum server to listen on +" + .to_string(), + ); + + retval.insert( + "attempt_time_per_block".to_string(), + " +#The amount of time, in seconds, to attempt to mine on a particular +#header before stopping and re-collecting transactions from the pool +" + .to_string(), + ); + + retval.insert( + "minimum_share_difficulty".to_string(), + " +#The minimum acceptable share difficulty to request from miners +" + .to_string(), + ); + + retval.insert( + "wallet_listener_url".to_string(), + " +#the wallet receiver to which coinbase rewards will be sent +" + .to_string(), + ); + + retval.insert( + "burn_reward".to_string(), + " +#whether to ignore the reward (mostly for testing) +" + .to_string(), + ); + + retval.insert( + "[wallet]".to_string(), + " +######################################### +### WALLET CONFIGURATION ### +######################################### +" + .to_string(), + ); + + retval.insert( + "api_listen_interface".to_string(), + " +# Host IP for wallet listener, change to \"0.0.0.0\" to receive grins +" + .to_string(), + ); + + retval.insert( + "api_listen_port".to_string(), + " +# Port for wallet listener +" + .to_string(), + ); + + retval.insert( + "check_node_api_http_addr".to_string(), + " +# Where the wallet should find a running node +" + .to_string(), + ); + + retval.insert( + "data_file_dir".to_string(), + " +# Where to find wallet files (seed, data, etc) +" + .to_string(), + ); + + retval.insert( + "[logging]".to_string(), + " +######################################### +### LOGGING CONFIGURATION ### +######################################### +" + .to_string(), + ); + + retval.insert( + "log_to_stdout".to_string(), + " +# Whether to log to stdout +" + .to_string(), + ); + + retval.insert( + "stdout_log_level".to_string(), + " +# Log level for stdout: Critical, Error, Warning, Info, Debug, Trace +" + .to_string(), + ); + + retval.insert( + "log_to_file".to_string(), + " +# Whether to log to a file +" + .to_string(), + ); + + retval.insert( + "file_log_level".to_string(), + " +# Log level for file: Critical, Error, Warning, Info, Debug, Trace +" + .to_string(), + ); + + retval.insert( + "log_file_path".to_string(), + " +# Log file path +" + .to_string(), + ); + + retval.insert( + "log_file_append".to_string(), + " +# Whether to append to the log file (true), or replace it on every run (false) +" + .to_string(), + ); + + retval +} + +fn get_key(line: &str) -> String { + if line.contains("[") && line.contains("]") { + return line.to_owned(); + } else if line.contains("=") { + return line.split("=").collect::>()[0].trim().to_owned(); + } else { + return "NOT_FOUND".to_owned(); + } +} + +pub fn insert_comments(orig: String) -> String { + let comments = comments(); + let lines: Vec<&str> = orig.split("\n").collect(); + let mut out_lines = vec![]; + for l in lines { + let key = get_key(l); + if let Some(v) = comments.get(&key) { + out_lines.push(v.to_owned()); + } + out_lines.push(l.to_owned()); + out_lines.push("\n".to_owned()); + } + let mut ret_val = String::from(""); + for l in out_lines { + ret_val.push_str(&l); + } + ret_val.to_owned() +} diff --git a/config/src/config.rs b/config/src/config.rs index 85329c9de..36bf7933f 100644 --- a/config/src/config.rs +++ b/config/src/config.rs @@ -14,33 +14,123 @@ //! Configuration file management -use dirs; use std::env; -use std::fs::File; +use std::fs::{self, File}; +use std::io::prelude::*; use std::io::Read; use std::path::PathBuf; use toml; -use servers::{ServerConfig, StratumServerConfig}; -use types::{ConfigError, ConfigMembers, GlobalConfig}; +use comments::insert_comments; +use servers::ServerConfig; +use types::{ + ConfigError, ConfigMembers, GlobalConfig, GlobalWalletConfig, GlobalWalletConfigMembers, +}; use util::LoggingConfig; use wallet::WalletConfig; /// The default file name to use when trying to derive -/// the config file location - -const CONFIG_FILE_NAME: &'static str = "grin.toml"; +/// the node config file location +pub const SERVER_CONFIG_FILE_NAME: &'static str = "grin-server.toml"; +/// And a wallet configuration file name +pub const WALLET_CONFIG_FILE_NAME: &'static str = "grin-wallet.toml"; +const SERVER_LOG_FILE_NAME: &'static str = "grin-server.log"; +const WALLET_LOG_FILE_NAME: &'static str = "grin-wallet.log"; const GRIN_HOME: &'static str = ".grin"; +const GRIN_CHAIN_DIR: &'static str = "chain_data"; +const GRIN_WALLET_DIR: &'static str = "wallet_data"; + +fn get_grin_path() -> Result { + // Check if grin dir exists + let grin_path = { + match env::home_dir() { + Some(mut p) => { + p.push(GRIN_HOME); + p + } + None => { + let mut pb = PathBuf::new(); + pb.push(GRIN_HOME); + pb + } + } + }; + // Create if the default path doesn't exist + if !grin_path.exists() { + fs::create_dir_all(grin_path.clone())?; + } + Ok(grin_path) +} + +fn check_config_current_dir(path: &str) -> Option { + let p = env::current_dir(); + let mut c = match p { + Ok(c) => c, + Err(_) => { + return None; + } + }; + c.push(path); + if c.exists() { + return Some(c); + } + None +} + +/// Handles setup and detection of paths for node +pub fn initial_setup_server() -> Result { + // Use config file if current directory if it exists, .grin home otherwise + if let Some(p) = check_config_current_dir(SERVER_CONFIG_FILE_NAME) { + GlobalConfig::new(p.to_str().unwrap()) + } else { + // Check if grin dir exists + let grin_path = get_grin_path()?; + + // Get path to default config file + let mut config_path = grin_path.clone(); + config_path.push(SERVER_CONFIG_FILE_NAME); + + // Spit it out if it doesn't exist + if !config_path.exists() { + let mut default_config = GlobalConfig::default(); + // update paths relative to current dir + default_config.update_paths(&grin_path); + default_config.write_to_file(config_path.to_str().unwrap())?; + } + GlobalConfig::new(config_path.to_str().unwrap()) + } +} + +/// Handles setup and detection of paths for wallet +pub fn initial_setup_wallet() -> Result { + // Use config file if current directory if it exists, .grin home otherwise + if let Some(p) = check_config_current_dir(WALLET_CONFIG_FILE_NAME) { + GlobalWalletConfig::new(p.to_str().unwrap()) + } else { + // Check if grin dir exists + let grin_path = get_grin_path()?; + + // Get path to default config file + let mut config_path = grin_path.clone(); + config_path.push(WALLET_CONFIG_FILE_NAME); + + // Spit it out if it doesn't exist + if !config_path.exists() { + let mut default_config = GlobalWalletConfig::default(); + // update paths relative to current dir + default_config.update_paths(&grin_path); + default_config.write_to_file(config_path.to_str().unwrap())?; + } + GlobalWalletConfig::new(config_path.to_str().unwrap()) + } +} /// Returns the defaults, as strewn throughout the code - impl Default for ConfigMembers { fn default() -> ConfigMembers { ConfigMembers { server: ServerConfig::default(), - mining_server: Some(StratumServerConfig::default()), logging: Some(LoggingConfig::default()), - wallet: WalletConfig::default(), } } } @@ -54,55 +144,29 @@ impl Default for GlobalConfig { } } -impl GlobalConfig { - /// Need to decide on rules where to read the config file from, - /// but will take a stab at logic for now - - fn derive_config_location(&mut self) -> Result<(), ConfigError> { - // First, check working directory - let mut config_path = env::current_dir().unwrap(); - config_path.push(CONFIG_FILE_NAME); - if config_path.exists() { - self.config_file_path = Some(config_path); - return Ok(()); +impl Default for GlobalWalletConfigMembers { + fn default() -> GlobalWalletConfigMembers { + GlobalWalletConfigMembers { + logging: Some(LoggingConfig::default()), + wallet: WalletConfig::default(), } - // Next, look in directory of executable - let mut config_path = env::current_exe().unwrap(); - config_path.pop(); - config_path.push(CONFIG_FILE_NAME); - if config_path.exists() { - self.config_file_path = Some(config_path); - return Ok(()); - } - // Then look in {user_home}/.grin - let config_path = dirs::home_dir(); - if let Some(mut p) = config_path { - p.push(GRIN_HOME); - p.push(CONFIG_FILE_NAME); - if p.exists() { - self.config_file_path = Some(p); - return Ok(()); - } - } - - // Give up - Err(ConfigError::FileNotFoundError(String::from(""))) } +} - /// Takes the path to a config file, or if NONE, tries to determine a config - /// file based on rules in derive_config_location - pub fn new(file_path: Option<&str>) -> Result { +impl Default for GlobalWalletConfig { + fn default() -> GlobalWalletConfig { + GlobalWalletConfig { + config_file_path: None, + members: Some(GlobalWalletConfigMembers::default()), + } + } +} + +impl GlobalConfig { + /// Requires the path to a config file + pub fn new(file_path: &str) -> Result { let mut return_value = GlobalConfig::default(); - if let Some(fp) = file_path { - return_value.config_file_path = Some(PathBuf::from(&fp)); - } else { - let _result = return_value.derive_config_location(); - } - - // No attempt at a config file, just return defaults - if let None = return_value.config_file_path { - return Ok(return_value); - } + return_value.config_file_path = Some(PathBuf::from(&file_path)); // Config file path is given but not valid let config_file = return_value.config_file_path.clone().unwrap(); @@ -124,10 +188,7 @@ impl GlobalConfig { file.read_to_string(&mut contents)?; let decoded: Result = toml::from_str(&contents); match decoded { - Ok(mut gc) => { - // Put the struct back together, because the config - // file was flattened a bit - gc.server.stratum_mining_config = gc.mining_server.clone(); + Ok(gc) => { self.members = Some(gc); return Ok(self); } @@ -147,6 +208,37 @@ impl GlobalConfig { } } + /// Update paths + pub fn update_paths(&mut self, grin_home: &PathBuf) { + // need to update server chain path + let mut chain_path = grin_home.clone(); + chain_path.push(GRIN_CHAIN_DIR); + self.members.as_mut().unwrap().server.db_root = chain_path.to_str().unwrap().to_owned(); + let mut log_path = grin_home.clone(); + log_path.push(SERVER_LOG_FILE_NAME); + self.members + .as_mut() + .unwrap() + .logging + .as_mut() + .unwrap() + .log_file_path = log_path.to_str().unwrap().to_owned(); + } + + /// Enable mining + pub fn stratum_enabled(&mut self) -> bool { + return self + .members + .as_mut() + .unwrap() + .server + .stratum_mining_config + .as_mut() + .unwrap() + .enable_stratum_server + .unwrap(); + } + /// Serialize config pub fn ser_config(&mut self) -> Result { let encoded: Result = @@ -162,20 +254,101 @@ impl GlobalConfig { } } - /*pub fn wallet_enabled(&mut self) -> bool { - return self.members.as_mut().unwrap().wallet.as_mut().unwrap().enable_wallet; - }*/ - - /// Enable mining - pub fn stratum_enabled(&mut self) -> bool { - return self - .members - .as_mut() - .unwrap() - .mining_server - .as_mut() - .unwrap() - .enable_stratum_server - .unwrap(); + /// Write configuration to a file + pub fn write_to_file(&mut self, name: &str) -> Result<(), ConfigError> { + let conf_out = self.ser_config()?; + let conf_out = insert_comments(conf_out); + let mut file = File::create(name)?; + file.write_all(conf_out.as_bytes())?; + Ok(()) + } +} + +/// TODO: Properly templatize these structs (if it's worth the effort) +impl GlobalWalletConfig { + /// Requires the path to a config file + pub fn new(file_path: &str) -> Result { + let mut return_value = GlobalWalletConfig::default(); + return_value.config_file_path = Some(PathBuf::from(&file_path)); + + // Config file path is given but not valid + let config_file = return_value.config_file_path.clone().unwrap(); + if !config_file.exists() { + return Err(ConfigError::FileNotFoundError(String::from( + config_file.to_str().unwrap(), + ))); + } + + // Try to parse the config file if it exists, explode if it does exist but + // something's wrong with it + return_value.read_config() + } + + /// Read config + fn read_config(mut self) -> Result { + let mut file = File::open(self.config_file_path.as_mut().unwrap())?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + let decoded: Result = toml::from_str(&contents); + match decoded { + Ok(gc) => { + self.members = Some(gc); + return Ok(self); + } + Err(e) => { + return Err(ConfigError::ParseError( + String::from( + self.config_file_path + .as_mut() + .unwrap() + .to_str() + .unwrap() + .clone(), + ), + String::from(format!("{}", e)), + )); + } + } + } + + /// Update paths + pub fn update_paths(&mut self, wallet_home: &PathBuf) { + let mut wallet_path = wallet_home.clone(); + wallet_path.push(GRIN_WALLET_DIR); + self.members.as_mut().unwrap().wallet.data_file_dir = + wallet_path.to_str().unwrap().to_owned(); + let mut log_path = wallet_home.clone(); + log_path.push(WALLET_LOG_FILE_NAME); + self.members + .as_mut() + .unwrap() + .logging + .as_mut() + .unwrap() + .log_file_path = log_path.to_str().unwrap().to_owned(); + } + + /// Serialize config + pub fn ser_config(&mut self) -> Result { + let encoded: Result = + toml::to_string(self.members.as_mut().unwrap()); + match encoded { + Ok(enc) => return Ok(enc), + Err(e) => { + return Err(ConfigError::SerializationError(String::from(format!( + "{}", + e + )))); + } + } + } + + /// Write configuration to a file + pub fn write_to_file(&mut self, name: &str) -> Result<(), ConfigError> { + let conf_out = self.ser_config()?; + let conf_out = insert_comments(conf_out); + let mut file = File::create(name)?; + file.write_all(conf_out.as_bytes())?; + Ok(()) } } diff --git a/config/src/lib.rs b/config/src/lib.rs index bc1840cc9..63e93005d 100644 --- a/config/src/lib.rs +++ b/config/src/lib.rs @@ -20,7 +20,6 @@ #![deny(unused_mut)] #![warn(missing_docs)] -extern crate serde; #[macro_use] extern crate serde_derive; extern crate dirs; @@ -31,7 +30,9 @@ extern crate grin_servers as servers; extern crate grin_util as util; extern crate grin_wallet as wallet; +mod comments; pub mod config; pub mod types; -pub use types::{ConfigError, ConfigMembers, GlobalConfig}; +pub use config::{initial_setup_server, initial_setup_wallet}; +pub use types::{ConfigError, ConfigMembers, GlobalConfig, GlobalWalletConfig}; diff --git a/config/src/types.rs b/config/src/types.rs index ad88826df..56a10408b 100644 --- a/config/src/types.rs +++ b/config/src/types.rs @@ -18,7 +18,7 @@ use std::fmt; use std::io; use std::path::PathBuf; -use servers::{ServerConfig, StratumServerConfig}; +use servers::ServerConfig; use util::LoggingConfig; use wallet::WalletConfig; @@ -92,14 +92,25 @@ pub struct ConfigMembers { /// Server config #[serde(default)] pub server: ServerConfig, - /// Mining config - pub mining_server: Option, /// Logging config pub logging: Option, +} - /// Wallet config. May eventually need to be moved to its own thing. Or not. - /// Depends on whether we end up starting the wallet in its own process but - /// with the same lifecycle as the server. +/// Wallet should be split into a separate configuration file +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct GlobalWalletConfig { + /// Keep track of the file we've read + pub config_file_path: Option, + /// Wallet members + pub members: Option, +} + +/// Wallet internal members +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct GlobalWalletConfigMembers { + /// Wallet configuration #[serde(default)] pub wallet: WalletConfig, + /// Logging config + pub logging: Option, } diff --git a/config/tests/config.rs b/config/tests/config.rs deleted file mode 100644 index 86dd66271..000000000 --- a/config/tests/config.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -extern crate grin_config as config; - -use config::GlobalConfig; - -#[test] -fn file_config_equal_to_defaults() { - let global_config_without_file = GlobalConfig::default(); - - let global_config_with_file = GlobalConfig::new(Some("../grin.toml")).unwrap_or_else(|e| { - panic!("Error parsing config file: {}", e); - }); - - assert_eq!( - global_config_without_file.members, - global_config_with_file.members - ); -} diff --git a/grin.toml b/grin.toml deleted file mode 100644 index 876a7b52e..000000000 --- a/grin.toml +++ /dev/null @@ -1,183 +0,0 @@ -# Sample Server Configuration File for Grin -# -# When running the grin executable without specifying any command line -# arguments, it will look for this file in three places, in the following -# order: -# -# -The working directory -# -The directory in which the executable resides -# -[user home]/.grin -# - -######################################### -### SERVER CONFIGURATION ### -######################################### - -#Server connection details -[server] - -#the address on which services will listen, e.g. Transaction Pool - -api_http_addr = "127.0.0.1:13413" - -#the directory, relative to current, in which the grin blockchain -#is stored - -db_root = ".grin" - -#The chain type, which defines the genesis block and the set of cuckoo -#parameters used for mining. Can be: -#AutomatedTesting - For CI builds and instant blockchain creation -#UserTesting - For regular user testing (cuckoo 16) -#Testnet1 - Testnet1 genesis block (cuckoo 16) -#Testnet2 - Testnet2 genesis block (cuckoo 30) - -chain_type = "Testnet3" - -#The chain validation mode, defines how often (if at all) we -#want to run a full chain validation. Can be: -#"EveryBlock" - run full chain validation when processing each block (except during sync) -#"Disabled" - disable full chain validation (just run regular block validation) -#chain_validation_mode = "Disabled" - -#run the node in "full archive" mode (default is fast-sync, pruned node) -#archive_mode = false - -#skip waiting for sync on startup, (optional param, mostly for testing) -skip_sync_wait = false - -#whether to run the ncurses TUI. Ncurses must be installed and this -#will also disable logging to stdout -run_tui = true - -#Whether to run the wallet listener with the server by default -run_wallet_listener = true - -# Whether to run the web-wallet API (will only run on localhost) -# grin wallet web will run this automatically, so this should -# only be set to true for test/development purposes -run_wallet_owner_api = false - -#Whether to run a test miner. This is only for developer testing (chaintype -#usertesting) at cuckoo 16, and will only mine into the default wallet port. -#real mining should use the standalone grin-miner -run_test_miner = false - -#test miner wallet URL (burns if this doesn't exist) -#test_miner_wallet_url = "http://127.0.0.1:13415" - -[server.dandelion_config] -#dandelion relay time (choose new relay peer every n secs) -relay_secs = 600 - -#fluff and broadcast after embargo expires if tx not seen on network -embargo_secs = 180 - -#run dandelion stem/fluff processing every n secs (stem tx aggregation in this window) -patience_secs = 10 - -#dandelion stem probability (stem 90% of the time, fluff 10% of the time) -stem_probability = 90 - -#The P2P server details (i.e. the server that communicates with other -#grin server nodes -[server.p2p_config] - -host = "0.0.0.0" -port = 13414 - -#How to seed this server, can be None, List, WebStatic or DNSSeed -# -#seeding_type = "None" - -#If seeding_type = List, the list of peers to connect to. -# -#seeds = ["192.168.0.1:13414","192.168.0.2:13414"] - -#7 = Bit flags for FULL_NODE, this structure needs to be changed -#internally to make it more configurable -capabilities = [7] - -#hardcoded peer lists for allow/deny -#will *only* connect to peers in allow list -#peers_allow = ["192.168.0.1:13414", "192.168.0.2:13414"] -#will *never* connect to peers in deny list -#peers_deny = ["192.168.0.3:13414", "192.168.0.4:13414"] -#a list of preferred peers to connect to -#peers_preferred = ["192.168.0.1:13414","192.168.0.2:13414"] - -#how long a banned peer should stay banned -#ban_window = 10800 - -#maximum number of peers -#peer_max_count = 25 - -#preferred minimum number of peers (we'll actively keep trying to add peers -#until we get to at least this number -#peer_min_preferred_count = 8 - -########################################### -### STRATUM MINING SERVER CONFIGURATION ### -########################################### -[mining_server] - -#flag whether stratum server is enabled -enable_stratum_server = true - -#what port and address for the stratum server to listen on -stratum_server_addr = "127.0.0.1:13416" - -#The amount of time, in seconds, to attempt to mine on a particular -#header before stopping and re-collecting transactions from the pool -attempt_time_per_block = 15 - -#The minimum acceptable share difficulty to request from miners -minimum_share_difficulty = 1 - -#the wallet receiver to which coinbase rewards will be sent -wallet_listener_url = "http://127.0.0.1:13415" - -#whether to ignore the reward (mostly for testing) -burn_reward = false - -######################################### -### WALLET CONFIGURATION ### -######################################### - -[wallet] - -# Host IP for wallet listener, change to "0.0.0.0" to receive grins -api_listen_interface = "127.0.0.1" - -# Port for wallet listener -api_listen_port = 13415 - -# Where the wallet should find a running node -check_node_api_http_addr = "http://127.0.0.1:13413" - -# Where to find wallet files (seed, data, etc) -data_file_dir = "." - -######################################### -### LOGGING CONFIGURATION ### -######################################### - -[logging] - -# Whether to log to stdout -log_to_stdout = true - -# Log level for stdout: Critical, Error, Warning, Info, Debug, Trace -stdout_log_level = "Warning" - -# Whether to log to a file -log_to_file = true - -# Log level for file: Critical, Error, Warning, Info, Debug, Trace -file_log_level = "Debug" - -# Log file path -log_file_path = "grin.log" - -# Whether to append to the log file (true), or replace it on every run (false) -log_file_append = true diff --git a/p2p/src/types.rs b/p2p/src/types.rs index e37c751c7..69245cbe0 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -142,7 +142,7 @@ impl Default for P2PConfig { } /// Note certain fields are options just so they don't have to be -/// included in grin.toml, but we don't want them to ever return none +/// included in grin-server.toml, but we don't want them to ever return none impl P2PConfig { /// return ban window pub fn ban_window(&self) -> i64 { diff --git a/servers/src/common/stats.rs b/servers/src/common/stats.rs index 896b9ef0c..6c9368aea 100644 --- a/servers/src/common/stats.rs +++ b/servers/src/common/stats.rs @@ -201,8 +201,8 @@ impl Default for StratumStats { is_running: false, num_workers: 0, block_height: 0, - network_difficulty: 0, - cuckoo_size: 0, + network_difficulty: 1000, + cuckoo_size: 30, worker_stats: Vec::new(), } } diff --git a/servers/src/common/types.rs b/servers/src/common/types.rs index 0111e7fe3..26729f458 100644 --- a/servers/src/common/types.rs +++ b/servers/src/common/types.rs @@ -117,26 +117,12 @@ pub struct ServerConfig { #[serde(default)] pub chain_type: ChainTypes, - /// Whether this node is a full archival node or a fast-sync, pruned node - pub archive_mode: Option, - /// Automatically run full chain validation during normal block processing? #[serde(default)] pub chain_validation_mode: ChainValidationMode, - /// Configuration for the peer-to-peer server - pub p2p_config: p2p::P2PConfig, - - /// Configuration for the mining daemon - pub stratum_mining_config: Option, - - /// Transaction pool configuration - #[serde(default)] - pub pool_config: pool::PoolConfig, - - /// Dandelion configuration - #[serde(default)] - pub dandelion_config: pool::DandelionConfig, + /// Whether this node is a full archival node or a fast-sync, pruned node + pub archive_mode: Option, /// Whether to skip the sync timeout on startup /// (To assist testing on solo chains) @@ -160,23 +146,38 @@ pub struct ServerConfig { /// Test miner wallet URL pub test_miner_wallet_url: Option, + + /// Configuration for the peer-to-peer server + pub p2p_config: p2p::P2PConfig, + + /// Transaction pool configuration + #[serde(default)] + pub pool_config: pool::PoolConfig, + + /// Dandelion configuration + #[serde(default)] + pub dandelion_config: pool::DandelionConfig, + + /// Configuration for the mining daemon + #[serde(default)] + pub stratum_mining_config: Option, } impl Default for ServerConfig { fn default() -> ServerConfig { ServerConfig { - db_root: ".grin".to_string(), + db_root: "grin_chain".to_string(), api_http_addr: "127.0.0.1:13413".to_string(), p2p_config: p2p::P2PConfig::default(), dandelion_config: pool::DandelionConfig::default(), stratum_mining_config: Some(StratumServerConfig::default()), chain_type: ChainTypes::default(), - archive_mode: None, + archive_mode: Some(false), chain_validation_mode: ChainValidationMode::default(), pool_config: pool::PoolConfig::default(), skip_sync_wait: Some(false), run_tui: Some(true), - run_wallet_listener: Some(true), + run_wallet_listener: Some(false), run_wallet_owner_api: Some(false), use_db_wallet: None, run_test_miner: Some(false), @@ -217,7 +218,7 @@ impl Default for StratumServerConfig { burn_reward: false, attempt_time_per_block: 15, minimum_share_difficulty: 1, - enable_stratum_server: Some(true), + enable_stratum_server: Some(false), stratum_server_addr: Some("127.0.0.1:13416".to_string()), } } diff --git a/servers/src/grin/server.rs b/servers/src/grin/server.rs index 23f95ab3c..705a5bd0b 100644 --- a/servers/src/grin/server.rs +++ b/servers/src/grin/server.rs @@ -65,19 +65,21 @@ impl Server { where F: FnMut(Arc), { - let mut mining_config = config.stratum_mining_config.clone(); + let mining_config = config.stratum_mining_config.clone(); let enable_test_miner = config.run_test_miner; let test_miner_wallet_url = config.test_miner_wallet_url.clone(); let serv = Arc::new(Server::new(config)?); - let enable_stratum_server = mining_config.as_mut().unwrap().enable_stratum_server; - if let Some(s) = enable_stratum_server { - if s { - { - let mut stratum_stats = serv.state_info.stratum_stats.write().unwrap(); - stratum_stats.is_enabled = true; + if let Some(c) = mining_config { + let enable_stratum_server = c.enable_stratum_server; + if let Some(s) = enable_stratum_server { + if s { + { + let mut stratum_stats = serv.state_info.stratum_stats.write().unwrap(); + stratum_stats.is_enabled = true; + } + serv.start_stratum_server(c.clone()); } - serv.start_stratum_server(mining_config.clone().unwrap()); } } diff --git a/src/bin/cmd/config.rs b/src/bin/cmd/config.rs new file mode 100644 index 000000000..3e993244f --- /dev/null +++ b/src/bin/cmd/config.rs @@ -0,0 +1,71 @@ +// Copyright 2018 The Grin Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Grin configuation file output command +use config::{GlobalConfig, GlobalWalletConfig}; +use std::env; + +/// Create a config file in the current directory +pub fn config_command_server(file_name: &str) { + let mut default_config = GlobalConfig::default(); + let current_dir = env::current_dir().unwrap_or_else(|e| { + panic!("Error creating config file: {}", e); + }); + let mut config_file_name = current_dir.clone(); + config_file_name.push(file_name); + if config_file_name.exists() { + panic!( + "{} already exists in the current directory. Please remove it first", + file_name + ); + } + default_config.update_paths(¤t_dir); + default_config + .write_to_file(config_file_name.to_str().unwrap()) + .unwrap_or_else(|e| { + panic!("Error creating config file: {}", e); + }); + + println!( + "{} file configured and created in current directory", + file_name + ); +} + +/// Create a config file in the current directory +pub fn config_command_wallet(file_name: &str) { + let mut default_config = GlobalWalletConfig::default(); + let current_dir = env::current_dir().unwrap_or_else(|e| { + panic!("Error creating config file: {}", e); + }); + let mut config_file_name = current_dir.clone(); + config_file_name.push(file_name); + if config_file_name.exists() { + panic!( + "{} already exists in the target directory. Please remove it first", + file_name + ); + } + default_config.update_paths(¤t_dir); + default_config + .write_to_file(config_file_name.to_str().unwrap()) + .unwrap_or_else(|e| { + panic!("Error creating config file: {}", e); + }); + + println!( + "File {} configured and created", + config_file_name.to_str().unwrap(), + ); +} diff --git a/src/bin/cmd/mod.rs b/src/bin/cmd/mod.rs index 801dcc36b..510df4f3c 100644 --- a/src/bin/cmd/mod.rs +++ b/src/bin/cmd/mod.rs @@ -13,9 +13,11 @@ // limitations under the License. mod client; +mod config; mod server; mod wallet; pub use self::client::client_command; +pub use self::config::{config_command_server, config_command_wallet}; pub use self::server::server_command; -pub use self::wallet::wallet_command; +pub use self::wallet::{seed_exists, wallet_command}; diff --git a/src/bin/cmd/server.rs b/src/bin/cmd/server.rs index dd0195d04..6525feca0 100644 --- a/src/bin/cmd/server.rs +++ b/src/bin/cmd/server.rs @@ -123,7 +123,7 @@ pub fn server_command(server_args: Option<&ArgMatches>, mut global_config: Globa } } - if let Some(true) = server_config.run_wallet_listener { + /*if let Some(true) = server_config.run_wallet_listener { let mut wallet_config = global_config.members.as_ref().unwrap().wallet.clone(); wallet::init_wallet_seed(wallet_config.clone()); let wallet = wallet::instantiate_wallet(wallet_config.clone(), ""); @@ -155,7 +155,7 @@ pub fn server_command(server_args: Option<&ArgMatches>, mut global_config: Globa ) }); }); - } + }*/ // start the server in the different run modes (interactive or daemon) if let Some(a) = server_args { diff --git a/src/bin/cmd/wallet.rs b/src/bin/cmd/wallet.rs index 27f093714..0444e819e 100644 --- a/src/bin/cmd/wallet.rs +++ b/src/bin/cmd/wallet.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::path::PathBuf; /// Wallet commands processing use std::process::exit; use std::sync::{Arc, Mutex}; @@ -20,7 +21,7 @@ use std::time::Duration; use clap::ArgMatches; -use config::GlobalConfig; +use config::GlobalWalletConfig; use core::core; use grin_wallet::{self, controller, display, libwallet}; use grin_wallet::{HTTPWalletClient, LMDBBackend, WalletConfig, WalletInst, WalletSeed}; @@ -28,12 +29,22 @@ use keychain; use servers::start_webwallet_server; use util::LOGGER; -pub fn init_wallet_seed(wallet_config: WalletConfig) { +pub fn _init_wallet_seed(wallet_config: WalletConfig) { if let Err(_) = WalletSeed::from_file(&wallet_config) { WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file."); }; } +pub fn seed_exists(wallet_config: WalletConfig) -> bool { + let mut data_file_dir = PathBuf::new(); + data_file_dir.push(wallet_config.data_file_dir); + data_file_dir.push(grin_wallet::SEED_FILE); + if data_file_dir.exists() { + true + } else { + false + } +} pub fn instantiate_wallet( wallet_config: WalletConfig, passphrase: &str, @@ -49,7 +60,7 @@ pub fn instantiate_wallet( warn!(LOGGER, "Migration successful. Using LMDB Wallet backend"); } warn!(LOGGER, "Please check the results of the migration process using `grin wallet info` and `grin wallet outputs`"); - warn!(LOGGER, "If anything went wrong, you can try again by deleting the `wallet_data` directory and running a wallet command"); + warn!(LOGGER, "If anything went wrong, you can try again by deleting the `db` directory and running a wallet command"); warn!(LOGGER, "If all is okay, you can move/backup/delete all files in the wallet directory EXCEPT FOR wallet.seed"); } let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr); @@ -63,9 +74,9 @@ pub fn instantiate_wallet( Box::new(db_wallet) } -pub fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { +pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) { // just get defaults from the global config - let mut wallet_config = global_config.members.unwrap().wallet; + let mut wallet_config = config.members.unwrap().wallet; if wallet_args.is_present("external") { wallet_config.api_listen_interface = "0.0.0.0".to_string(); diff --git a/src/bin/grin.rs b/src/bin/grin.rs index e653e4855..2bc216f1a 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -41,7 +41,7 @@ pub mod tui; use clap::{App, Arg, SubCommand}; -use config::GlobalConfig; +use config::config::{SERVER_CONFIG_FILE_NAME, WALLET_CONFIG_FILE_NAME}; use core::global; use util::{init_logger, LOGGER}; @@ -81,10 +81,14 @@ fn main() { .version(crate_version!()) .author("The Grin Team") .about("Lightweight implementation of the MimbleWimble protocol.") - // specification of all the server commands and options .subcommand(SubCommand::with_name("server") .about("Control the Grin server") + .arg(Arg::with_name("config_file") + .short("c") + .long("config_file") + .help("Path to a grin-server.toml configuration file") + .takes_value(true)) .arg(Arg::with_name("port") .short("p") .long("port") @@ -104,7 +108,9 @@ fn main() { .short("w") .long("wallet_url") .help("The wallet listener to which mining rewards will be sent") - .takes_value(true)) + .takes_value(true)) + .subcommand(SubCommand::with_name("config") + .about("Generate a configuration grin-server.toml file in the current directory")) .subcommand(SubCommand::with_name("start") .about("Start the Grin server as a daemon")) .subcommand(SubCommand::with_name("stop") @@ -274,85 +280,114 @@ fn main() { .about("basic wallet contents summary")) .subcommand(SubCommand::with_name("init") - .about("Initialize a new wallet seed file and database.")) + .about("Initialize a new wallet seed file and database.") + .arg(Arg::with_name("here") + .short("h") + .long("here") + .help("Create wallet files in the current directory instead of the default ~/.grin directory") + .takes_value(false))) .subcommand(SubCommand::with_name("restore") .about("Attempt to restore wallet contents from the chain using seed and password. \ NOTE: Backup wallet.* and run `wallet listen` before running restore."))) .get_matches(); + let mut wallet_config = None; + let mut node_config = None; - // load a global config object, - // then modify that object with any switches - // found so that the switches override the - // global config file - - // This will return a global config object, - // which will either contain defaults for all // of the config structures or a - // configuration - // read from a config file - - let mut global_config = GlobalConfig::new(None).unwrap_or_else(|e| { - panic!("Error parsing config file: {}", e); - }); - - // initialize the logger - let mut log_conf = global_config - .members - .as_mut() - .unwrap() - .logging - .clone() - .unwrap(); - let run_tui = global_config.members.as_mut().unwrap().server.run_tui; - if run_tui.is_some() && run_tui.unwrap() && args.subcommand().0 != "wallet" { - log_conf.log_to_stdout = false; - log_conf.tui_running = Some(true); + // Deal with configuration file creation + match args.subcommand() { + ("server", Some(server_args)) => { + // If it's just a server config command, do it and exit + if let ("config", Some(_)) = server_args.subcommand() { + cmd::config_command_server(SERVER_CONFIG_FILE_NAME); + return; + } + } + ("wallet", Some(wallet_args)) => { + // wallet init command should spit out its config file then continue + // (if desired) + if let ("init", Some(init_args)) = wallet_args.subcommand() { + if init_args.is_present("here") { + cmd::config_command_wallet(WALLET_CONFIG_FILE_NAME); + } + } + } + _ => {} + } + + match args.subcommand() { + // If it's a wallet command, try and load a wallet config file + ("wallet", Some(wallet_args)) => { + let mut w = config::initial_setup_wallet().unwrap_or_else(|e| { + panic!("Error loading wallet configuration: {}", e); + }); + if !cmd::seed_exists(w.members.as_ref().unwrap().wallet.clone()) { + if let ("init", Some(_)) = wallet_args.subcommand() { + } else { + println!("Wallet seed file doesn't exist. Run `grin wallet -p [password] init` first"); + return; + } + } + let mut l = w.members.as_mut().unwrap().logging.clone().unwrap(); + l.tui_running = Some(false); + init_logger(Some(l)); + warn!( + LOGGER, + "Using wallet configuration file at {}", + w.config_file_path.as_ref().unwrap().to_str().unwrap() + ); + wallet_config = Some(w); + } + // Otherwise load up the node config as usual + _ => { + let mut s = config::initial_setup_server().unwrap_or_else(|e| { + panic!("Error loading server configuration: {}", e); + }); + let mut l = s.members.as_mut().unwrap().logging.clone().unwrap(); + let run_tui = s.members.as_mut().unwrap().server.run_tui; + if let Some(true) = run_tui { + l.log_to_stdout = false; + l.tui_running = Some(true); + } + init_logger(Some(l)); + global::set_mining_mode(s.members.as_mut().unwrap().server.clone().chain_type); + if let Some(file_path) = &s.config_file_path { + info!( + LOGGER, + "Using configuration file at {}", + file_path.to_str().unwrap() + ); + } else { + info!(LOGGER, "Node configuration file not found, using default"); + } + node_config = Some(s); + } } - init_logger(Some(log_conf)); - global::set_mining_mode( - global_config - .members - .as_mut() - .unwrap() - .server - .clone() - .chain_type, - ); log_build_info(); - if let Some(file_path) = &global_config.config_file_path { - info!( - LOGGER, - "Found configuration file at {}", - file_path.to_str().unwrap() - ); - } else { - info!(LOGGER, "configuration file not found, using default"); - } - match args.subcommand() { // server commands and options ("server", Some(server_args)) => { - cmd::server_command(Some(server_args), global_config); + cmd::server_command(Some(server_args), node_config.unwrap()); } // client commands and options ("client", Some(client_args)) => { - cmd::client_command(client_args, global_config); + cmd::client_command(client_args, node_config.unwrap()); } // client commands and options ("wallet", Some(wallet_args)) => { - cmd::wallet_command(wallet_args, global_config); + cmd::wallet_command(wallet_args, wallet_config.unwrap()); } // If nothing is specified, try to just use the config file instead // this could possibly become the way to configure most things // with most command line options being phased out _ => { - cmd::server_command(None, global_config); + cmd::server_command(None, node_config.unwrap()); } } } diff --git a/wallet/src/db_migrate.rs b/wallet/src/db_migrate.rs index f0ad39634..a5cff059b 100644 --- a/wallet/src/db_migrate.rs +++ b/wallet/src/db_migrate.rs @@ -36,7 +36,7 @@ use store::{self, to_key}; const DETAIL_FILE: &'static str = "wallet.det"; const DAT_FILE: &'static str = "wallet.dat"; const SEED_FILE: &'static str = "wallet.seed"; -const DB_DIR: &'static str = "wallet_data"; +const DB_DIR: &'static str = "db"; const OUTPUT_PREFIX: u8 = 'o' as u8; const DERIV_PREFIX: u8 = 'd' as u8; const CONFIRMED_HEIGHT_PREFIX: u8 = 'c' as u8; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index acb7a6564..7f8487d44 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -63,7 +63,7 @@ pub use libwallet::types::{ BlockFees, CbData, WalletBackend, WalletClient, WalletInfo, WalletInst, }; pub use lmdb_wallet::{wallet_db_exists, LMDBBackend}; -pub use types::{WalletConfig, WalletSeed}; +pub use types::{WalletConfig, WalletSeed, SEED_FILE}; // temporary pub use db_migrate::{migrate, needs_migrate}; diff --git a/wallet/src/libwallet/internal/restore.rs b/wallet/src/libwallet/internal/restore.rs index 658efd3ed..82c3a2ef9 100644 --- a/wallet/src/libwallet/internal/restore.rs +++ b/wallet/src/libwallet/internal/restore.rs @@ -159,7 +159,7 @@ where if !is_empty { error!( LOGGER, - "Not restoring. Please back up and remove existing wallet_data directory first." + "Not restoring. Please back up and remove existing db directory first." ); return Ok(()); } diff --git a/wallet/src/lmdb_wallet.rs b/wallet/src/lmdb_wallet.rs index 4287e96da..00d6e3311 100644 --- a/wallet/src/lmdb_wallet.rs +++ b/wallet/src/lmdb_wallet.rs @@ -27,7 +27,7 @@ use libwallet::{internal, Error, ErrorKind}; use types::{WalletConfig, WalletSeed}; use util::secp::pedersen; -pub const DB_DIR: &'static str = "wallet_data"; +pub const DB_DIR: &'static str = "db"; const COMMITMENT_PREFIX: u8 = 'C' as u8; const OUTPUT_PREFIX: u8 = 'o' as u8;