From 784c5e34426e331979441af86c53ba7e54bb9f58 Mon Sep 17 00:00:00 2001 From: Ignotus Peverell Date: Fri, 3 Aug 2018 15:39:54 -0700 Subject: [PATCH] Cleanup Grin main binary crate (#1315) Split up each subcommand into its own file under the `cmd` subcrate. --- src/bin/{ => cmd}/client.rs | 39 ++- src/bin/cmd/mod.rs | 21 ++ src/bin/cmd/server.rs | 195 ++++++++++++++ src/bin/cmd/wallet.rs | 326 +++++++++++++++++++++++ src/bin/grin.rs | 516 +----------------------------------- 5 files changed, 589 insertions(+), 508 deletions(-) rename src/bin/{ => cmd}/client.rs (80%) create mode 100644 src/bin/cmd/mod.rs create mode 100644 src/bin/cmd/server.rs create mode 100644 src/bin/cmd/wallet.rs diff --git a/src/bin/client.rs b/src/bin/cmd/client.rs similarity index 80% rename from src/bin/client.rs rename to src/bin/cmd/client.rs index 4d0bc99c7..78a20ef2c 100644 --- a/src/bin/client.rs +++ b/src/bin/cmd/client.rs @@ -12,13 +12,50 @@ // See the License for the specific language governing permissions and // limitations under the License. -extern crate term; +/// Grin client commands processing use std::net::SocketAddr; +use clap::ArgMatches; + use api; +use config::GlobalConfig; use p2p; use servers::ServerConfig; +use term; + +pub fn client_command(client_args: &ArgMatches, global_config: GlobalConfig) { + // just get defaults from the global config + let server_config = global_config.members.unwrap().server; + + match client_args.subcommand() { + ("status", Some(_)) => { + show_status(&server_config); + } + ("listconnectedpeers", Some(_)) => { + list_connected_peers(&server_config); + } + ("ban", Some(peer_args)) => { + let peer = peer_args.value_of("peer").unwrap(); + + if let Ok(addr) = peer.parse() { + ban_peer(&server_config, &addr); + } else { + panic!("Invalid peer address format"); + } + } + ("unban", Some(peer_args)) => { + let peer = peer_args.value_of("peer").unwrap(); + + if let Ok(addr) = peer.parse() { + unban_peer(&server_config, &addr); + } else { + panic!("Invalid peer address format"); + } + } + _ => panic!("Unknown client command, use 'grin help client' for details"), + } +} pub fn show_status(config: &ServerConfig) { println!(); diff --git a/src/bin/cmd/mod.rs b/src/bin/cmd/mod.rs new file mode 100644 index 000000000..801dcc36b --- /dev/null +++ b/src/bin/cmd/mod.rs @@ -0,0 +1,21 @@ +// 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. + +mod client; +mod server; +mod wallet; + +pub use self::client::client_command; +pub use self::server::server_command; +pub use self::wallet::wallet_command; diff --git a/src/bin/cmd/server.rs b/src/bin/cmd/server.rs new file mode 100644 index 000000000..f5cdf071e --- /dev/null +++ b/src/bin/cmd/server.rs @@ -0,0 +1,195 @@ +// 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 server commands processing + +use std::env::current_dir; +use std::process::exit; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +use ctrlc; +use clap::ArgMatches; +use daemonize::Daemonize; + +use cmd::wallet; +use config::GlobalConfig; +use core::global; +use grin_wallet::controller; +use servers; +use tui::ui; +use util::LOGGER; + +/// wrap below to allow UI to clean up on stop +fn start_server(config: servers::ServerConfig) { + start_server_tui(config); + // Just kill process for now, otherwise the process + // hangs around until sigint because the API server + // currently has no shutdown facility + println!("Shutting down..."); + thread::sleep(Duration::from_millis(1000)); + println!("Shutdown complete."); + exit(0); +} + +fn start_server_tui(config: servers::ServerConfig) { + // Run the UI controller.. here for now for simplicity to access + // everything it might need + if config.run_tui.is_some() && config.run_tui.unwrap() { + println!("Starting GRIN in UI mode..."); + servers::Server::start(config, |serv: Arc| { + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + let _ = thread::Builder::new() + .name("ui".to_string()) + .spawn(move || { + let mut controller = ui::Controller::new().unwrap_or_else(|e| { + panic!("Error loading UI controller: {}", e); + }); + controller.run(serv.clone(), r); + }); + ctrlc::set_handler(move || { + running.store(false, Ordering::SeqCst); + }).expect("Error setting Ctrl-C handler"); + }).unwrap(); + } else { + servers::Server::start(config, |serv: Arc| { + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + ctrlc::set_handler(move || { + r.store(false, Ordering::SeqCst); + }).expect("Error setting Ctrl-C handler"); + while running.load(Ordering::SeqCst) { + thread::sleep(Duration::from_secs(1)); + } + warn!(LOGGER, "Received SIGINT (Ctrl+C)."); + serv.stop(); + }).unwrap(); + } +} + +/// Handles the server part of the command line, mostly running, starting and +/// stopping the Grin blockchain server. Processes all the command line +/// arguments to build a proper configuration and runs Grin with that +/// configuration. +pub fn server_command(server_args: Option<&ArgMatches>, mut global_config: GlobalConfig) { + global::set_mining_mode( + global_config + .members + .as_mut() + .unwrap() + .server + .clone() + .chain_type, + ); + + // just get defaults from the global config + let mut server_config = global_config.members.as_ref().unwrap().server.clone(); + + if let Some(a) = server_args { + if let Some(port) = a.value_of("port") { + server_config.p2p_config.port = port.parse().unwrap(); + } + + if let Some(api_port) = a.value_of("api_port") { + let default_ip = "0.0.0.0"; + server_config.api_http_addr = format!("{}:{}", default_ip, api_port); + } + + if let Some(wallet_url) = a.value_of("wallet_url") { + server_config + .stratum_mining_config + .as_mut() + .unwrap() + .wallet_listener_url = wallet_url.to_string(); + } + + if let Some(seeds) = a.values_of("seed") { + server_config.seeding_type = servers::Seeding::List; + server_config.seeds = Some(seeds.map(|s| s.to_string()).collect()); + } + } + + 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(), ""); + + let _ = thread::Builder::new() + .name("wallet_listener".to_string()) + .spawn(move || { + controller::foreign_listener(wallet, &wallet_config.api_listen_addr()) + .unwrap_or_else(|e| { + panic!( + "Error creating wallet listener: {:?} Config: {:?}", + e, wallet_config + ) + }); + }); + } + if let Some(true) = server_config.run_wallet_owner_api { + let mut wallet_config = global_config.members.unwrap().wallet; + let wallet = wallet::instantiate_wallet(wallet_config.clone(), ""); + wallet::init_wallet_seed(wallet_config.clone()); + + let _ = thread::Builder::new() + .name("wallet_owner_listener".to_string()) + .spawn(move || { + controller::owner_listener(wallet, "127.0.0.1:13420").unwrap_or_else(|e| { + panic!( + "Error creating wallet api listener: {:?} Config: {:?}", + e, wallet_config + ) + }); + }); + } + + // start the server in the different run modes (interactive or daemon) + if let Some(a) = server_args { + match a.subcommand() { + ("run", _) => { + start_server(server_config); + } + ("start", _) => { + let daemonize = Daemonize::new() + .pid_file("/tmp/grin.pid") + .chown_pid_file(true) + .working_directory(current_dir().unwrap()) + .privileged_action(move || { + start_server(server_config.clone()); + loop { + thread::sleep(Duration::from_secs(60)); + } + }); + match daemonize.start() { + Ok(_) => info!(LOGGER, "Grin server successfully started."), + Err(e) => error!(LOGGER, "Error starting: {}", e), + } + } + ("stop", _) => println!("TODO. Just 'kill $pid' for now. Maybe /tmp/grin.pid is $pid"), + (cmd, _) => { + println!(":: {:?}", server_args); + panic!( + "Unknown server command '{}', use 'grin help server' for details", + cmd + ); + } + } + } else { + start_server(server_config); + } +} + diff --git a/src/bin/cmd/wallet.rs b/src/bin/cmd/wallet.rs new file mode 100644 index 000000000..dd96441f3 --- /dev/null +++ b/src/bin/cmd/wallet.rs @@ -0,0 +1,326 @@ +// 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. + +/// Wallet commands processing + +use std::process::exit; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use clap::ArgMatches; + +use config::GlobalConfig; +use core::core; +use grin_wallet::{self, libwallet, controller, display}; +use grin_wallet::{HTTPWalletClient, LMDBBackend, WalletConfig, WalletInst, WalletSeed}; +use keychain; +use util::LOGGER; + +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 instantiate_wallet( + wallet_config: WalletConfig, + passphrase: &str, +) -> Box> { + if grin_wallet::needs_migrate(&wallet_config.data_file_dir) { + // Migrate wallet automatically + warn!(LOGGER, "Migrating legacy File-Based wallet to LMDB Format"); + if let Err(e) = grin_wallet::migrate(&wallet_config.data_file_dir, passphrase) { + error!(LOGGER, "Error while trying to migrate wallet: {:?}", e); + error!(LOGGER, "Please ensure your file wallet files exist and are not corrupted, and that your password is correct"); + panic!(); + } else { + 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 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); + let db_wallet = LMDBBackend::new(wallet_config.clone(), "", client).unwrap_or_else(|e| { + panic!( + "Error creating DB wallet: {} Config: {:?}", + e, wallet_config + ); + }); + info!(LOGGER, "Using LMDB Backend for wallet"); + Box::new(db_wallet) +} + +pub fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { + // just get defaults from the global config + let mut wallet_config = global_config.members.unwrap().wallet; + + if wallet_args.is_present("external") { + wallet_config.api_listen_interface = "0.0.0.0".to_string(); + } + + if let Some(dir) = wallet_args.value_of("dir") { + wallet_config.data_file_dir = dir.to_string().clone(); + } + + if let Some(sa) = wallet_args.value_of("api_server_address") { + wallet_config.check_node_api_http_addr = sa.to_string().clone(); + } + + let mut show_spent = false; + if wallet_args.is_present("show_spent") { + show_spent = true; + } + + // Derive the keychain based on seed from seed file and specified passphrase. + // Generate the initial wallet seed if we are running "wallet init". + if let ("init", Some(_)) = wallet_args.subcommand() { + WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file."); + info!(LOGGER, "Wallet seed file created"); + let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr); + let _: LMDBBackend = + LMDBBackend::new(wallet_config.clone(), "", client).unwrap_or_else(|e| { + panic!( + "Error creating DB for wallet: {} Config: {:?}", + e, wallet_config + ); + }); + info!(LOGGER, "Wallet database backend created"); + // give logging thread a moment to catch up + thread::sleep(Duration::from_millis(200)); + // we are done here with creating the wallet, so just return + return; + } + + let passphrase = wallet_args + .value_of("pass") + .expect("Failed to read passphrase."); + + // Handle listener startup commands + { + let wallet = instantiate_wallet(wallet_config.clone(), passphrase); + match wallet_args.subcommand() { + ("listen", Some(listen_args)) => { + if let Some(port) = listen_args.value_of("port") { + wallet_config.api_listen_port = port.parse().unwrap(); + } + controller::foreign_listener(wallet, &wallet_config.api_listen_addr()) + .unwrap_or_else(|e| { + panic!( + "Error creating wallet listener: {:?} Config: {:?}", + e, wallet_config + ) + }); + } + ("owner_api", Some(_api_args)) => { + controller::owner_listener(wallet, "127.0.0.1:13420").unwrap_or_else(|e| { + panic!( + "Error creating wallet api listener: {:?} Config: {:?}", + e, wallet_config + ) + }); + } + _ => {} + }; + } + + // Handle single-use (command line) owner commands + let wallet = Arc::new(Mutex::new(instantiate_wallet( + wallet_config.clone(), + passphrase, + ))); + let res = controller::owner_single_use(wallet, |api| { + match wallet_args.subcommand() { + ("send", Some(send_args)) => { + let amount = send_args + .value_of("amount") + .expect("Amount to send required"); + let amount = core::amount_from_hr_string(amount) + .expect("Could not parse amount as a number with optional decimal point."); + let minimum_confirmations: u64 = send_args + .value_of("minimum_confirmations") + .unwrap() + .parse() + .expect("Could not parse minimum_confirmations as a whole number."); + let selection_strategy = send_args + .value_of("selection_strategy") + .expect("Selection strategy required"); + let dest = send_args + .value_of("dest") + .expect("Destination wallet address required"); + let mut fluff = false; + if send_args.is_present("fluff") { + fluff = true; + } + let max_outputs = 500; + let result = api.issue_send_tx( + amount, + minimum_confirmations, + dest, + max_outputs, + selection_strategy == "all", + ); + let slate = match result { + Ok(s) => { + info!( + LOGGER, + "Tx created: {} grin to {} (strategy '{}')", + core::amount_to_hr_string(amount), + dest, + selection_strategy, + ); + s + } + Err(e) => { + error!(LOGGER, "Tx not created: {:?}", e); + match e.kind() { + // user errors, don't backtrace + libwallet::ErrorKind::NotEnoughFunds { .. } => {} + libwallet::ErrorKind::FeeDispute { .. } => {} + libwallet::ErrorKind::FeeExceedsAmount { .. } => {} + _ => { + // otherwise give full dump + error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); + } + }; + panic!(); + } + }; + let result = api.post_tx(&slate, fluff); + match result { + Ok(_) => { + info!(LOGGER, "Tx sent",); + Ok(()) + } + Err(e) => { + error!(LOGGER, "Tx not sent: {:?}", e); + Err(e) + } + } + } + ("burn", Some(send_args)) => { + let amount = send_args + .value_of("amount") + .expect("Amount to burn required"); + let amount = core::amount_from_hr_string(amount) + .expect("Could not parse amount as number with optional decimal point."); + let minimum_confirmations: u64 = send_args + .value_of("minimum_confirmations") + .unwrap() + .parse() + .expect("Could not parse minimum_confirmations as a whole number."); + let max_outputs = 500; + api.issue_burn_tx(amount, minimum_confirmations, max_outputs) + .unwrap_or_else(|e| { + panic!("Error burning tx: {:?} Config: {:?}", e, wallet_config) + }); + Ok(()) + } + ("info", Some(_)) => { + let (validated, wallet_info) = + api.retrieve_summary_info(true).unwrap_or_else(|e| { + panic!( + "Error getting wallet info: {:?} Config: {:?}", + e, wallet_config + ) + }); + display::info(&wallet_info, validated); + Ok(()) + } + ("outputs", Some(_)) => { + let (height, _) = api.node_height()?; + let (validated, outputs) = api.retrieve_outputs(show_spent, true, None)?; + let _res = + display::outputs(height, validated, outputs).unwrap_or_else(|e| { + panic!( + "Error getting wallet outputs: {:?} Config: {:?}", + e, wallet_config + ) + }); + Ok(()) + } + ("txs", Some(txs_args)) => { + let tx_id = match txs_args.value_of("id") { + None => None, + Some(tx) => match tx.parse() { + Ok(t) => Some(t), + Err(_) => panic!("Unable to parse argument 'id' as a number"), + }, + }; + let (height, _) = api.node_height()?; + let (validated, txs) = api.retrieve_txs(true, tx_id)?; + let include_status = !tx_id.is_some(); + let _res = display::txs(height, validated, txs, include_status) + .unwrap_or_else(|e| { + panic!( + "Error getting wallet outputs: {} Config: {:?}", + e, wallet_config + ) + }); + // if given a particular transaction id, also get and display associated + // inputs/outputs + if tx_id.is_some() { + let (_, outputs) = api.retrieve_outputs(true, false, tx_id)?; + let _res = + display::outputs(height, validated, outputs).unwrap_or_else(|e| { + panic!( + "Error getting wallet outputs: {} Config: {:?}", + e, wallet_config + ) + }); + }; + Ok(()) + } + ("cancel", Some(tx_args)) => { + let tx_id = tx_args + .value_of("id") + .expect("'id' argument (-i) is required."); + let tx_id = tx_id.parse().expect("Could not parse id parameter."); + let result = api.cancel_tx(tx_id); + match result { + Ok(_) => { + info!(LOGGER, "Transaction {} Cancelled", tx_id); + Ok(()) + } + Err(e) => { + error!(LOGGER, "TX Cancellation failed: {}", e); + Err(e) + } + } + } + ("restore", Some(_)) => { + let result = api.restore(); + match result { + Ok(_) => { + info!(LOGGER, "Wallet restore complete",); + Ok(()) + } + Err(e) => { + error!(LOGGER, "Wallet restore failed: {:?}", e); + error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); + Err(e) + } + } + } + _ => panic!("Unknown wallet command, use 'grin help wallet' for details"), + } + }); + // we need to give log output a chance to catch up before exiting + thread::sleep(Duration::from_millis(100)); + + if res.is_err() { + exit(1); + } +} diff --git a/src/bin/grin.rs b/src/bin/grin.rs index f05917e1e..f16aca155 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -15,6 +15,7 @@ //! Main for building the binary of a Grin peer-to-peer node. extern crate blake2_rfc as blake2; +extern crate chrono; #[macro_use] extern crate clap; extern crate ctrlc; @@ -24,7 +25,7 @@ extern crate serde; extern crate serde_json; #[macro_use] extern crate slog; -extern crate chrono; +extern crate term; extern crate grin_api as api; extern crate grin_config as config; @@ -33,27 +34,16 @@ extern crate grin_keychain as keychain; extern crate grin_p2p as p2p; extern crate grin_servers as servers; extern crate grin_util as util; -extern crate grin_wallet as wallet; +extern crate grin_wallet; -mod client; +mod cmd; pub mod tui; -use std::env::current_dir; -use std::process::exit; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::Duration; - -use clap::{App, Arg, ArgMatches, SubCommand}; -use daemonize::Daemonize; +use clap::{App, Arg, SubCommand}; use config::GlobalConfig; -use core::core::amount_to_hr_string; use core::global; -use tui::ui; use util::{init_logger, LOGGER}; -use wallet::{libwallet, HTTPWalletClient, LMDBBackend, WalletConfig, WalletInst}; // include build information pub mod built_info { @@ -86,54 +76,6 @@ fn log_build_info() { trace!(LOGGER, "{}", deps); } -/// wrap below to allow UI to clean up on stop -fn start_server(config: servers::ServerConfig) { - start_server_tui(config); - // Just kill process for now, otherwise the process - // hangs around until sigint because the API server - // currently has no shutdown facility - println!("Shutting down..."); - thread::sleep(Duration::from_millis(1000)); - println!("Shutdown complete."); - exit(0); -} - -fn start_server_tui(config: servers::ServerConfig) { - // Run the UI controller.. here for now for simplicity to access - // everything it might need - if config.run_tui.is_some() && config.run_tui.unwrap() { - println!("Starting GRIN in UI mode..."); - servers::Server::start(config, |serv: Arc| { - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); - let _ = thread::Builder::new() - .name("ui".to_string()) - .spawn(move || { - let mut controller = ui::Controller::new().unwrap_or_else(|e| { - panic!("Error loading UI controller: {}", e); - }); - controller.run(serv.clone(), r); - }); - ctrlc::set_handler(move || { - running.store(false, Ordering::SeqCst); - }).expect("Error setting Ctrl-C handler"); - }).unwrap(); - } else { - servers::Server::start(config, |serv: Arc| { - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); - ctrlc::set_handler(move || { - r.store(false, Ordering::SeqCst); - }).expect("Error setting Ctrl-C handler"); - while running.load(Ordering::SeqCst) { - thread::sleep(Duration::from_secs(1)); - } - warn!(LOGGER, "Received SIGINT (Ctrl+C)."); - serv.stop(); - }).unwrap(); - } -} - fn main() { let args = App::new("Grin") .version(crate_version!()) @@ -372,466 +314,26 @@ fn main() { match args.subcommand() { // server commands and options ("server", Some(server_args)) => { - server_command(Some(server_args), global_config); + cmd::server_command(Some(server_args), global_config); } // client commands and options ("client", Some(client_args)) => { - client_command(client_args, global_config); + cmd::client_command(client_args, global_config); } // client commands and options ("wallet", Some(wallet_args)) => { - wallet_command(wallet_args, global_config); + cmd::wallet_command(wallet_args, global_config); } // 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 _ => { - server_command(None, global_config); + cmd::server_command(None, global_config); } } } -/// Handles the server part of the command line, mostly running, starting and -/// stopping the Grin blockchain server. Processes all the command line -/// arguments -/// to build a proper configuration and runs Grin with that configuration. -fn server_command(server_args: Option<&ArgMatches>, mut global_config: GlobalConfig) { - global::set_mining_mode( - global_config - .members - .as_mut() - .unwrap() - .server - .clone() - .chain_type, - ); - // just get defaults from the global config - let mut server_config = global_config.members.as_ref().unwrap().server.clone(); - - if let Some(a) = server_args { - if let Some(port) = a.value_of("port") { - server_config.p2p_config.port = port.parse().unwrap(); - } - - if let Some(api_port) = a.value_of("api_port") { - let default_ip = "0.0.0.0"; - server_config.api_http_addr = format!("{}:{}", default_ip, api_port); - } - - if let Some(wallet_url) = a.value_of("wallet_url") { - server_config - .stratum_mining_config - .as_mut() - .unwrap() - .wallet_listener_url = wallet_url.to_string(); - } - - if let Some(seeds) = a.values_of("seed") { - server_config.seeding_type = servers::Seeding::List; - server_config.seeds = Some(seeds.map(|s| s.to_string()).collect()); - } - } - - if let Some(true) = server_config.run_wallet_listener { - let mut wallet_config = global_config.members.as_ref().unwrap().wallet.clone(); - init_wallet_seed(wallet_config.clone()); - let wallet = instantiate_wallet(wallet_config.clone(), ""); - - let _ = thread::Builder::new() - .name("wallet_listener".to_string()) - .spawn(move || { - wallet::controller::foreign_listener(wallet, &wallet_config.api_listen_addr()) - .unwrap_or_else(|e| { - panic!( - "Error creating wallet listener: {:?} Config: {:?}", - e, wallet_config - ) - }); - }); - } - if let Some(true) = server_config.run_wallet_owner_api { - let mut wallet_config = global_config.members.unwrap().wallet; - let wallet = instantiate_wallet(wallet_config.clone(), ""); - init_wallet_seed(wallet_config.clone()); - - let _ = thread::Builder::new() - .name("wallet_owner_listener".to_string()) - .spawn(move || { - wallet::controller::owner_listener(wallet, "127.0.0.1:13420").unwrap_or_else(|e| { - panic!( - "Error creating wallet api listener: {:?} Config: {:?}", - e, wallet_config - ) - }); - }); - } - - // start the server in the different run modes (interactive or daemon) - if let Some(a) = server_args { - match a.subcommand() { - ("run", _) => { - start_server(server_config); - } - ("start", _) => { - let daemonize = Daemonize::new() - .pid_file("/tmp/grin.pid") - .chown_pid_file(true) - .working_directory(current_dir().unwrap()) - .privileged_action(move || { - start_server(server_config.clone()); - loop { - thread::sleep(Duration::from_secs(60)); - } - }); - match daemonize.start() { - Ok(_) => info!(LOGGER, "Grin server successfully started."), - Err(e) => error!(LOGGER, "Error starting: {}", e), - } - } - ("stop", _) => println!("TODO. Just 'kill $pid' for now. Maybe /tmp/grin.pid is $pid"), - (cmd, _) => { - println!(":: {:?}", server_args); - panic!( - "Unknown server command '{}', use 'grin help server' for details", - cmd - ); - } - } - } else { - start_server(server_config); - } -} - -fn client_command(client_args: &ArgMatches, global_config: GlobalConfig) { - // just get defaults from the global config - let server_config = global_config.members.unwrap().server; - - match client_args.subcommand() { - ("status", Some(_)) => { - client::show_status(&server_config); - } - ("listconnectedpeers", Some(_)) => { - client::list_connected_peers(&server_config); - } - ("ban", Some(peer_args)) => { - let peer = peer_args.value_of("peer").unwrap(); - - if let Ok(addr) = peer.parse() { - client::ban_peer(&server_config, &addr); - } else { - panic!("Invalid peer address format"); - } - } - ("unban", Some(peer_args)) => { - let peer = peer_args.value_of("peer").unwrap(); - - if let Ok(addr) = peer.parse() { - client::unban_peer(&server_config, &addr); - } else { - panic!("Invalid peer address format"); - } - } - _ => panic!("Unknown client command, use 'grin help client' for details"), - } -} - -fn init_wallet_seed(wallet_config: WalletConfig) { - if let Err(_) = wallet::WalletSeed::from_file(&wallet_config) { - wallet::WalletSeed::init_file(&wallet_config).expect("Failed to create wallet seed file."); - }; -} - -fn instantiate_wallet( - wallet_config: WalletConfig, - passphrase: &str, -) -> Box> { - if wallet::needs_migrate(&wallet_config.data_file_dir) { - // Migrate wallet automatically - warn!(LOGGER, "Migrating legacy File-Based wallet to LMDB Format"); - if let Err(e) = wallet::migrate(&wallet_config.data_file_dir, passphrase) { - error!(LOGGER, "Error while trying to migrate wallet: {:?}", e); - error!(LOGGER, "Please ensure your file wallet files exist and are not corrupted, and that your password is correct"); - panic!(); - } else { - 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 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); - let db_wallet = LMDBBackend::new(wallet_config.clone(), "", client).unwrap_or_else(|e| { - panic!( - "Error creating DB wallet: {} Config: {:?}", - e, wallet_config - ); - }); - info!(LOGGER, "Using LMDB Backend for wallet"); - Box::new(db_wallet) -} - -fn wallet_command(wallet_args: &ArgMatches, global_config: GlobalConfig) { - // just get defaults from the global config - let mut wallet_config = global_config.members.unwrap().wallet; - - if wallet_args.is_present("external") { - wallet_config.api_listen_interface = "0.0.0.0".to_string(); - } - - if let Some(dir) = wallet_args.value_of("dir") { - wallet_config.data_file_dir = dir.to_string().clone(); - } - - if let Some(sa) = wallet_args.value_of("api_server_address") { - wallet_config.check_node_api_http_addr = sa.to_string().clone(); - } - - let mut show_spent = false; - if wallet_args.is_present("show_spent") { - show_spent = true; - } - - // Derive the keychain based on seed from seed file and specified passphrase. - // Generate the initial wallet seed if we are running "wallet init". - if let ("init", Some(_)) = wallet_args.subcommand() { - wallet::WalletSeed::init_file(&wallet_config).expect("Failed to init wallet seed file."); - info!(LOGGER, "Wallet seed file created"); - let client = HTTPWalletClient::new(&wallet_config.check_node_api_http_addr); - let _: LMDBBackend = - LMDBBackend::new(wallet_config.clone(), "", client).unwrap_or_else(|e| { - panic!( - "Error creating DB for wallet: {} Config: {:?}", - e, wallet_config - ); - }); - info!(LOGGER, "Wallet database backend created"); - // give logging thread a moment to catch up - thread::sleep(Duration::from_millis(200)); - // we are done here with creating the wallet, so just return - return; - } - - let passphrase = wallet_args - .value_of("pass") - .expect("Failed to read passphrase."); - - // Handle listener startup commands - { - let wallet = instantiate_wallet(wallet_config.clone(), passphrase); - match wallet_args.subcommand() { - ("listen", Some(listen_args)) => { - if let Some(port) = listen_args.value_of("port") { - wallet_config.api_listen_port = port.parse().unwrap(); - } - wallet::controller::foreign_listener(wallet, &wallet_config.api_listen_addr()) - .unwrap_or_else(|e| { - panic!( - "Error creating wallet listener: {:?} Config: {:?}", - e, wallet_config - ) - }); - } - ("owner_api", Some(_api_args)) => { - wallet::controller::owner_listener(wallet, "127.0.0.1:13420").unwrap_or_else(|e| { - panic!( - "Error creating wallet api listener: {:?} Config: {:?}", - e, wallet_config - ) - }); - } - _ => {} - }; - } - - // Handle single-use (command line) owner commands - let wallet = Arc::new(Mutex::new(instantiate_wallet( - wallet_config.clone(), - passphrase, - ))); - let res = wallet::controller::owner_single_use(wallet, |api| { - match wallet_args.subcommand() { - ("send", Some(send_args)) => { - let amount = send_args - .value_of("amount") - .expect("Amount to send required"); - let amount = core::core::amount_from_hr_string(amount) - .expect("Could not parse amount as a number with optional decimal point."); - let minimum_confirmations: u64 = send_args - .value_of("minimum_confirmations") - .unwrap() - .parse() - .expect("Could not parse minimum_confirmations as a whole number."); - let selection_strategy = send_args - .value_of("selection_strategy") - .expect("Selection strategy required"); - let dest = send_args - .value_of("dest") - .expect("Destination wallet address required"); - let mut fluff = false; - if send_args.is_present("fluff") { - fluff = true; - } - let max_outputs = 500; - let result = api.issue_send_tx( - amount, - minimum_confirmations, - dest, - max_outputs, - selection_strategy == "all", - ); - let slate = match result { - Ok(s) => { - info!( - LOGGER, - "Tx created: {} grin to {} (strategy '{}')", - amount_to_hr_string(amount), - dest, - selection_strategy, - ); - s - } - Err(e) => { - error!(LOGGER, "Tx not created: {:?}", e); - match e.kind() { - // user errors, don't backtrace - libwallet::ErrorKind::NotEnoughFunds { .. } => {} - libwallet::ErrorKind::FeeDispute { .. } => {} - libwallet::ErrorKind::FeeExceedsAmount { .. } => {} - _ => { - // otherwise give full dump - error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); - } - }; - panic!(); - } - }; - let result = api.post_tx(&slate, fluff); - match result { - Ok(_) => { - info!(LOGGER, "Tx sent",); - Ok(()) - } - Err(e) => { - error!(LOGGER, "Tx not sent: {:?}", e); - Err(e) - } - } - } - ("burn", Some(send_args)) => { - let amount = send_args - .value_of("amount") - .expect("Amount to burn required"); - let amount = core::core::amount_from_hr_string(amount) - .expect("Could not parse amount as number with optional decimal point."); - let minimum_confirmations: u64 = send_args - .value_of("minimum_confirmations") - .unwrap() - .parse() - .expect("Could not parse minimum_confirmations as a whole number."); - let max_outputs = 500; - api.issue_burn_tx(amount, minimum_confirmations, max_outputs) - .unwrap_or_else(|e| { - panic!("Error burning tx: {:?} Config: {:?}", e, wallet_config) - }); - Ok(()) - } - ("info", Some(_)) => { - let (validated, wallet_info) = - api.retrieve_summary_info(true).unwrap_or_else(|e| { - panic!( - "Error getting wallet info: {:?} Config: {:?}", - e, wallet_config - ) - }); - wallet::display::info(&wallet_info, validated); - Ok(()) - } - ("outputs", Some(_)) => { - let (height, _) = api.node_height()?; - let (validated, outputs) = api.retrieve_outputs(show_spent, true, None)?; - let _res = - wallet::display::outputs(height, validated, outputs).unwrap_or_else(|e| { - panic!( - "Error getting wallet outputs: {:?} Config: {:?}", - e, wallet_config - ) - }); - Ok(()) - } - ("txs", Some(txs_args)) => { - let tx_id = match txs_args.value_of("id") { - None => None, - Some(tx) => match tx.parse() { - Ok(t) => Some(t), - Err(_) => panic!("Unable to parse argument 'id' as a number"), - }, - }; - let (height, _) = api.node_height()?; - let (validated, txs) = api.retrieve_txs(true, tx_id)?; - let include_status = !tx_id.is_some(); - let _res = wallet::display::txs(height, validated, txs, include_status) - .unwrap_or_else(|e| { - panic!( - "Error getting wallet outputs: {} Config: {:?}", - e, wallet_config - ) - }); - // if given a particular transaction id, also get and display associated - // inputs/outputs - if tx_id.is_some() { - let (_, outputs) = api.retrieve_outputs(true, false, tx_id)?; - let _res = - wallet::display::outputs(height, validated, outputs).unwrap_or_else(|e| { - panic!( - "Error getting wallet outputs: {} Config: {:?}", - e, wallet_config - ) - }); - }; - Ok(()) - } - ("cancel", Some(tx_args)) => { - let tx_id = tx_args - .value_of("id") - .expect("'id' argument (-i) is required."); - let tx_id = tx_id.parse().expect("Could not parse id parameter."); - let result = api.cancel_tx(tx_id); - match result { - Ok(_) => { - info!(LOGGER, "Transaction {} Cancelled", tx_id); - Ok(()) - } - Err(e) => { - error!(LOGGER, "TX Cancellation failed: {}", e); - Err(e) - } - } - } - ("restore", Some(_)) => { - let result = api.restore(); - match result { - Ok(_) => { - info!(LOGGER, "Wallet restore complete",); - Ok(()) - } - Err(e) => { - error!(LOGGER, "Wallet restore failed: {:?}", e); - error!(LOGGER, "Backtrace: {}", e.backtrace().unwrap()); - Err(e) - } - } - } - _ => panic!("Unknown wallet command, use 'grin help wallet' for details"), - } - }); - // we need to give log output a chance to catch up before exiting - thread::sleep(Duration::from_millis(100)); - - if res.is_err() { - exit(1); - } -}