mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
Cleanup Grin main binary crate (#1315)
Split up each subcommand into its own file under the `cmd` subcrate.
This commit is contained in:
parent
6a1d0e3354
commit
784c5e3442
5 changed files with 589 additions and 508 deletions
|
@ -12,13 +12,50 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
extern crate term;
|
/// Grin client commands processing
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use clap::ArgMatches;
|
||||||
|
|
||||||
use api;
|
use api;
|
||||||
|
use config::GlobalConfig;
|
||||||
use p2p;
|
use p2p;
|
||||||
use servers::ServerConfig;
|
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) {
|
pub fn show_status(config: &ServerConfig) {
|
||||||
println!();
|
println!();
|
21
src/bin/cmd/mod.rs
Normal file
21
src/bin/cmd/mod.rs
Normal file
|
@ -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;
|
195
src/bin/cmd/server.rs
Normal file
195
src/bin/cmd/server.rs
Normal file
|
@ -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<servers::Server>| {
|
||||||
|
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<servers::Server>| {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
326
src/bin/cmd/wallet.rs
Normal file
326
src/bin/cmd/wallet.rs
Normal file
|
@ -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<WalletInst<HTTPWalletClient, keychain::ExtKeychain>> {
|
||||||
|
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<HTTPWalletClient, keychain::ExtKeychain> =
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
516
src/bin/grin.rs
516
src/bin/grin.rs
|
@ -15,6 +15,7 @@
|
||||||
//! Main for building the binary of a Grin peer-to-peer node.
|
//! Main for building the binary of a Grin peer-to-peer node.
|
||||||
|
|
||||||
extern crate blake2_rfc as blake2;
|
extern crate blake2_rfc as blake2;
|
||||||
|
extern crate chrono;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate ctrlc;
|
extern crate ctrlc;
|
||||||
|
@ -24,7 +25,7 @@ extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate slog;
|
extern crate slog;
|
||||||
extern crate chrono;
|
extern crate term;
|
||||||
|
|
||||||
extern crate grin_api as api;
|
extern crate grin_api as api;
|
||||||
extern crate grin_config as config;
|
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_p2p as p2p;
|
||||||
extern crate grin_servers as servers;
|
extern crate grin_servers as servers;
|
||||||
extern crate grin_util as util;
|
extern crate grin_util as util;
|
||||||
extern crate grin_wallet as wallet;
|
extern crate grin_wallet;
|
||||||
|
|
||||||
mod client;
|
mod cmd;
|
||||||
pub mod tui;
|
pub mod tui;
|
||||||
|
|
||||||
use std::env::current_dir;
|
use clap::{App, Arg, SubCommand};
|
||||||
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 config::GlobalConfig;
|
use config::GlobalConfig;
|
||||||
use core::core::amount_to_hr_string;
|
|
||||||
use core::global;
|
use core::global;
|
||||||
use tui::ui;
|
|
||||||
use util::{init_logger, LOGGER};
|
use util::{init_logger, LOGGER};
|
||||||
use wallet::{libwallet, HTTPWalletClient, LMDBBackend, WalletConfig, WalletInst};
|
|
||||||
|
|
||||||
// include build information
|
// include build information
|
||||||
pub mod built_info {
|
pub mod built_info {
|
||||||
|
@ -86,54 +76,6 @@ fn log_build_info() {
|
||||||
trace!(LOGGER, "{}", deps);
|
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<servers::Server>| {
|
|
||||||
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<servers::Server>| {
|
|
||||||
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() {
|
fn main() {
|
||||||
let args = App::new("Grin")
|
let args = App::new("Grin")
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
|
@ -372,466 +314,26 @@ fn main() {
|
||||||
match args.subcommand() {
|
match args.subcommand() {
|
||||||
// server commands and options
|
// server commands and options
|
||||||
("server", Some(server_args)) => {
|
("server", Some(server_args)) => {
|
||||||
server_command(Some(server_args), global_config);
|
cmd::server_command(Some(server_args), global_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// client commands and options
|
// client commands and options
|
||||||
("client", Some(client_args)) => {
|
("client", Some(client_args)) => {
|
||||||
client_command(client_args, global_config);
|
cmd::client_command(client_args, global_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// client commands and options
|
// client commands and options
|
||||||
("wallet", Some(wallet_args)) => {
|
("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
|
// If nothing is specified, try to just use the config file instead
|
||||||
// this could possibly become the way to configure most things
|
// this could possibly become the way to configure most things
|
||||||
// with most command line options being phased out
|
// 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<WalletInst<HTTPWalletClient, keychain::ExtKeychain>> {
|
|
||||||
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<HTTPWalletClient, keychain::ExtKeychain> =
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue