Remove wallet code [1.1.0] (#2593)
* Bump version to 1.1.0 * remove wallet crate * wallet extracted + mine_block structs * remove wallet doc * remove unnecessary cargo deps * rustfmt * remove wallet from travis matrix * move integration tests into separate crate * rustfmt * move integration crate to wallet
This commit is contained in:
+1
-46
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
/// Grin configuration file output command
|
||||
use crate::config::{config, GlobalConfig, GlobalWalletConfig, GRIN_WALLET_DIR};
|
||||
use crate::config::GlobalConfig;
|
||||
use crate::core::global;
|
||||
use std::env;
|
||||
|
||||
@@ -43,48 +43,3 @@ pub fn config_command_server(chain_type: &global::ChainTypes, file_name: &str) {
|
||||
file_name
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a config file in the current directory
|
||||
pub fn config_command_wallet(chain_type: &global::ChainTypes, file_name: &str) {
|
||||
let mut default_config = GlobalWalletConfig::for_chain(chain_type);
|
||||
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);
|
||||
|
||||
let mut data_dir_name = current_dir.clone();
|
||||
data_dir_name.push(GRIN_WALLET_DIR);
|
||||
|
||||
if config_file_name.exists() && data_dir_name.exists() {
|
||||
panic!(
|
||||
"{} already exists in the target directory. Please remove it first",
|
||||
file_name
|
||||
);
|
||||
}
|
||||
|
||||
// just leave as is if file exists but there's no data dir
|
||||
if config_file_name.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
|
||||
let mut api_secret_path = current_dir.clone();
|
||||
api_secret_path.push(config::API_SECRET_FILE_NAME);
|
||||
if !api_secret_path.exists() {
|
||||
config::init_api_secret(&api_secret_path).unwrap();
|
||||
} else {
|
||||
config::check_api_secret(&api_secret_path).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -15,11 +15,7 @@
|
||||
mod client;
|
||||
mod config;
|
||||
mod server;
|
||||
mod wallet;
|
||||
mod wallet_args;
|
||||
mod wallet_tests;
|
||||
|
||||
pub use self::client::client_command;
|
||||
pub use self::config::{config_command_server, config_command_wallet};
|
||||
pub use self::config::config_command_server;
|
||||
pub use self::server::server_command;
|
||||
pub use self::wallet::{seed_exists, wallet_command};
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
/// Grin server commands processing
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
use std::env::current_dir;
|
||||
use std::process::exit;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
// 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.
|
||||
|
||||
use crate::cmd::wallet_args;
|
||||
use crate::config::GlobalWalletConfig;
|
||||
use clap::ArgMatches;
|
||||
use grin_wallet::{self, HTTPNodeClient, WalletConfig, WalletSeed};
|
||||
use std::path::PathBuf;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn _init_wallet_seed(wallet_config: WalletConfig, password: &str) {
|
||||
if let Err(_) = WalletSeed::from_file(&wallet_config, password) {
|
||||
WalletSeed::init_file(&wallet_config, 32, None, password)
|
||||
.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 wallet_command(wallet_args: &ArgMatches<'_>, config: GlobalWalletConfig) -> i32 {
|
||||
// just get defaults from the global config
|
||||
let wallet_config = config.members.unwrap().wallet;
|
||||
|
||||
// web wallet http server must be started from here
|
||||
// NB: Turned off for the time being
|
||||
/*let _ = match wallet_args.subcommand() {
|
||||
("web", Some(_)) => start_webwallet_server(),
|
||||
_ => {}
|
||||
};*/
|
||||
|
||||
let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||
let res = wallet_args::wallet_command(wallet_args, wallet_config, node_client);
|
||||
|
||||
// we need to give log output a chance to catch up before exiting
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
if let Err(e) = res {
|
||||
println!("Wallet command failed: {}", e);
|
||||
1
|
||||
} else {
|
||||
println!(
|
||||
"Command '{}' completed successfully",
|
||||
wallet_args.subcommand().0
|
||||
);
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -1,634 +0,0 @@
|
||||
// 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.
|
||||
|
||||
use crate::api::TLSConfig;
|
||||
use crate::util::file::get_first_line;
|
||||
use crate::util::{Mutex, ZeroingString};
|
||||
/// Argument parsing and error handling for wallet commands
|
||||
use clap::ArgMatches;
|
||||
use failure::Fail;
|
||||
use grin_core as core;
|
||||
use grin_keychain as keychain;
|
||||
use grin_wallet::{command, instantiate_wallet, NodeClient, WalletConfig, WalletInst, WalletSeed};
|
||||
use grin_wallet::{Error, ErrorKind};
|
||||
use linefeed::terminal::Signal;
|
||||
use linefeed::{Interface, ReadResult};
|
||||
use rpassword;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
// define what to do on argument error
|
||||
macro_rules! arg_parse {
|
||||
( $r:expr ) => {
|
||||
match $r {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
return Err(ErrorKind::ArgumentError(format!("{}", e)).into());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
/// Simple error definition, just so we can return errors from all commands
|
||||
/// and let the caller figure out what to do
|
||||
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
|
||||
pub enum ParseError {
|
||||
#[fail(display = "Invalid Arguments: {}", _0)]
|
||||
ArgumentError(String),
|
||||
#[fail(display = "Parsing IO error: {}", _0)]
|
||||
IOError(String),
|
||||
#[fail(display = "User Cancelled")]
|
||||
CancelledError,
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ParseError {
|
||||
fn from(e: std::io::Error) -> ParseError {
|
||||
ParseError::IOError(format!("{}", e))
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_password_stdout(prompt: &str) -> ZeroingString {
|
||||
ZeroingString::from(rpassword::prompt_password_stdout(prompt).unwrap())
|
||||
}
|
||||
|
||||
pub fn prompt_password(password: &Option<ZeroingString>) -> ZeroingString {
|
||||
match password {
|
||||
None => prompt_password_stdout("Password: "),
|
||||
Some(p) => p.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_password_confirm() -> ZeroingString {
|
||||
let mut first = ZeroingString::from("first");
|
||||
let mut second = ZeroingString::from("second");
|
||||
while first != second {
|
||||
first = prompt_password_stdout("Password: ");
|
||||
second = prompt_password_stdout("Confirm Password: ");
|
||||
}
|
||||
first
|
||||
}
|
||||
|
||||
fn prompt_replace_seed() -> Result<bool, ParseError> {
|
||||
let interface = Arc::new(Interface::new("replace_seed")?);
|
||||
interface.set_report_signal(Signal::Interrupt, true);
|
||||
interface.set_prompt("Replace seed? (y/n)> ")?;
|
||||
println!();
|
||||
println!("Existing wallet.seed file already exists. Continue?");
|
||||
println!("Continuing will back up your existing 'wallet.seed' file as 'wallet.seed.bak'");
|
||||
println!();
|
||||
loop {
|
||||
let res = interface.read_line()?;
|
||||
match res {
|
||||
ReadResult::Eof => return Ok(false),
|
||||
ReadResult::Signal(sig) => {
|
||||
if sig == Signal::Interrupt {
|
||||
interface.cancel_read_line()?;
|
||||
return Err(ParseError::CancelledError);
|
||||
}
|
||||
}
|
||||
ReadResult::Input(line) => match line.trim() {
|
||||
"Y" | "y" => return Ok(true),
|
||||
"N" | "n" => return Ok(false),
|
||||
_ => println!("Please respond y or n"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_recovery_phrase() -> Result<ZeroingString, ParseError> {
|
||||
let interface = Arc::new(Interface::new("recover")?);
|
||||
let mut phrase = ZeroingString::from("");
|
||||
interface.set_report_signal(Signal::Interrupt, true);
|
||||
interface.set_prompt("phrase> ")?;
|
||||
loop {
|
||||
println!("Please enter your recovery phrase:");
|
||||
let res = interface.read_line()?;
|
||||
match res {
|
||||
ReadResult::Eof => break,
|
||||
ReadResult::Signal(sig) => {
|
||||
if sig == Signal::Interrupt {
|
||||
interface.cancel_read_line()?;
|
||||
return Err(ParseError::CancelledError);
|
||||
}
|
||||
}
|
||||
ReadResult::Input(line) => {
|
||||
if WalletSeed::from_mnemonic(&line).is_ok() {
|
||||
phrase = ZeroingString::from(line);
|
||||
break;
|
||||
} else {
|
||||
println!();
|
||||
println!("Recovery word phrase is invalid.");
|
||||
println!();
|
||||
interface.set_buffer(&line)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(phrase)
|
||||
}
|
||||
|
||||
// instantiate wallet (needed by most functions)
|
||||
|
||||
pub fn inst_wallet(
|
||||
config: WalletConfig,
|
||||
g_args: &command::GlobalArgs,
|
||||
node_client: impl NodeClient + 'static,
|
||||
) -> Result<Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>, ParseError> {
|
||||
let passphrase = prompt_password(&g_args.password);
|
||||
let res = instantiate_wallet(config.clone(), node_client, &passphrase, &g_args.account);
|
||||
match res {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => {
|
||||
let msg = {
|
||||
match e.kind() {
|
||||
ErrorKind::Encryption => {
|
||||
format!("Error decrypting wallet seed (check provided password)")
|
||||
}
|
||||
_ => format!("Error instantiating wallet: {}", e),
|
||||
}
|
||||
};
|
||||
Err(ParseError::ArgumentError(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parses a required value, or throws error with message otherwise
|
||||
fn parse_required<'a>(args: &'a ArgMatches, name: &str) -> Result<&'a str, ParseError> {
|
||||
let arg = args.value_of(name);
|
||||
match arg {
|
||||
Some(ar) => Ok(ar),
|
||||
None => {
|
||||
let msg = format!("Value for argument '{}' is required in this context", name,);
|
||||
Err(ParseError::ArgumentError(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parses a number, or throws error with message otherwise
|
||||
fn parse_u64(arg: &str, name: &str) -> Result<u64, ParseError> {
|
||||
let val = arg.parse::<u64>();
|
||||
match val {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => {
|
||||
let msg = format!("Could not parse {} as a whole number. e={}", name, e);
|
||||
Err(ParseError::ArgumentError(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_global_args(
|
||||
config: &WalletConfig,
|
||||
args: &ArgMatches,
|
||||
) -> Result<command::GlobalArgs, ParseError> {
|
||||
let account = parse_required(args, "account")?;
|
||||
let mut show_spent = false;
|
||||
if args.is_present("show_spent") {
|
||||
show_spent = true;
|
||||
}
|
||||
let node_api_secret = get_first_line(config.node_api_secret_path.clone());
|
||||
let password = match args.value_of("pass") {
|
||||
None => None,
|
||||
Some(p) => Some(ZeroingString::from(p)),
|
||||
};
|
||||
|
||||
let tls_conf = match config.tls_certificate_file.clone() {
|
||||
None => None,
|
||||
Some(file) => {
|
||||
let key = match config.tls_certificate_key.clone() {
|
||||
Some(k) => k,
|
||||
None => {
|
||||
let msg = format!("Private key for certificate is not set");
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
};
|
||||
Some(TLSConfig::new(file, key))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(command::GlobalArgs {
|
||||
account: account.to_owned(),
|
||||
show_spent: show_spent,
|
||||
node_api_secret: node_api_secret,
|
||||
password: password,
|
||||
tls_conf: tls_conf,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_init_args(
|
||||
config: &WalletConfig,
|
||||
g_args: &command::GlobalArgs,
|
||||
args: &ArgMatches,
|
||||
) -> Result<command::InitArgs, ParseError> {
|
||||
if let Err(e) = WalletSeed::seed_file_exists(config) {
|
||||
let msg = format!("Not creating wallet - {}", e.inner);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
let list_length = match args.is_present("short_wordlist") {
|
||||
false => 32,
|
||||
true => 16,
|
||||
};
|
||||
let recovery_phrase = match args.is_present("recover") {
|
||||
true => Some(prompt_recovery_phrase()?),
|
||||
false => None,
|
||||
};
|
||||
|
||||
if recovery_phrase.is_some() {
|
||||
println!("Please provide a new password for the recovered wallet");
|
||||
} else {
|
||||
println!("Please enter a password for your new wallet");
|
||||
}
|
||||
|
||||
let password = match g_args.password.clone() {
|
||||
Some(p) => p,
|
||||
None => prompt_password_confirm(),
|
||||
};
|
||||
|
||||
Ok(command::InitArgs {
|
||||
list_length: list_length,
|
||||
password: password,
|
||||
config: config.clone(),
|
||||
recovery_phrase: recovery_phrase,
|
||||
restore: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_recover_args(
|
||||
config: &WalletConfig,
|
||||
g_args: &command::GlobalArgs,
|
||||
args: &ArgMatches,
|
||||
) -> Result<command::RecoverArgs, ParseError> {
|
||||
let (passphrase, recovery_phrase) = {
|
||||
match args.is_present("display") {
|
||||
true => (prompt_password(&g_args.password), None),
|
||||
false => {
|
||||
let cont = {
|
||||
if command::wallet_seed_exists(config).is_err() {
|
||||
prompt_replace_seed()?
|
||||
} else {
|
||||
true
|
||||
}
|
||||
};
|
||||
if !cont {
|
||||
return Err(ParseError::CancelledError);
|
||||
}
|
||||
let phrase = prompt_recovery_phrase()?;
|
||||
println!("Please provide a new password for the recovered wallet");
|
||||
(prompt_password_confirm(), Some(phrase.to_owned()))
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(command::RecoverArgs {
|
||||
passphrase: passphrase,
|
||||
recovery_phrase: recovery_phrase,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_listen_args(
|
||||
config: &mut WalletConfig,
|
||||
g_args: &mut command::GlobalArgs,
|
||||
args: &ArgMatches,
|
||||
) -> Result<command::ListenArgs, ParseError> {
|
||||
// listen args
|
||||
let pass = match g_args.password.clone() {
|
||||
Some(p) => Some(p.to_owned()),
|
||||
None => Some(prompt_password(&None)),
|
||||
};
|
||||
g_args.password = pass;
|
||||
if let Some(port) = args.value_of("port") {
|
||||
config.api_listen_port = port.parse().unwrap();
|
||||
}
|
||||
let method = parse_required(args, "method")?;
|
||||
Ok(command::ListenArgs {
|
||||
method: method.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_account_args(account_args: &ArgMatches) -> Result<command::AccountArgs, ParseError> {
|
||||
let create = match account_args.value_of("create") {
|
||||
None => None,
|
||||
Some(s) => Some(s.to_owned()),
|
||||
};
|
||||
Ok(command::AccountArgs { create: create })
|
||||
}
|
||||
|
||||
pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseError> {
|
||||
// amount
|
||||
let amount = parse_required(args, "amount")?;
|
||||
let amount = core::core::amount_from_hr_string(amount);
|
||||
let amount = match amount {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
let msg = format!(
|
||||
"Could not parse amount as a number with optional decimal point. e={}",
|
||||
e
|
||||
);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
};
|
||||
|
||||
// message
|
||||
let message = match args.is_present("message") {
|
||||
true => Some(args.value_of("message").unwrap().to_owned()),
|
||||
false => None,
|
||||
};
|
||||
|
||||
// minimum_confirmations
|
||||
let min_c = parse_required(args, "minimum_confirmations")?;
|
||||
let min_c = parse_u64(min_c, "minimum_confirmations")?;
|
||||
|
||||
// selection_strategy
|
||||
let selection_strategy = parse_required(args, "selection_strategy")?;
|
||||
|
||||
// estimate_selection_strategies
|
||||
let estimate_selection_strategies = args.is_present("estimate_selection_strategies");
|
||||
|
||||
// method
|
||||
let method = parse_required(args, "method")?;
|
||||
|
||||
// dest
|
||||
let dest = {
|
||||
if method == "self" {
|
||||
match args.value_of("dest") {
|
||||
Some(d) => d,
|
||||
None => "default",
|
||||
}
|
||||
} else {
|
||||
if !estimate_selection_strategies {
|
||||
parse_required(args, "dest")?
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
};
|
||||
if !estimate_selection_strategies
|
||||
&& method == "http"
|
||||
&& !dest.starts_with("http://")
|
||||
&& !dest.starts_with("https://")
|
||||
{
|
||||
let msg = format!(
|
||||
"HTTP Destination should start with http://: or https://: {}",
|
||||
dest,
|
||||
);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
|
||||
// change_outputs
|
||||
let change_outputs = parse_required(args, "change_outputs")?;
|
||||
let change_outputs = parse_u64(change_outputs, "change_outputs")? as usize;
|
||||
|
||||
// fluff
|
||||
let fluff = args.is_present("fluff");
|
||||
|
||||
// max_outputs
|
||||
let max_outputs = 500;
|
||||
|
||||
Ok(command::SendArgs {
|
||||
amount: amount,
|
||||
message: message,
|
||||
minimum_confirmations: min_c,
|
||||
selection_strategy: selection_strategy.to_owned(),
|
||||
estimate_selection_strategies,
|
||||
method: method.to_owned(),
|
||||
dest: dest.to_owned(),
|
||||
change_outputs: change_outputs,
|
||||
fluff: fluff,
|
||||
max_outputs: max_outputs,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_receive_args(receive_args: &ArgMatches) -> Result<command::ReceiveArgs, ParseError> {
|
||||
// message
|
||||
let message = match receive_args.is_present("message") {
|
||||
true => Some(receive_args.value_of("message").unwrap().to_owned()),
|
||||
false => None,
|
||||
};
|
||||
|
||||
// input
|
||||
let tx_file = parse_required(receive_args, "input")?;
|
||||
|
||||
// validate input
|
||||
if !Path::new(&tx_file).is_file() {
|
||||
let msg = format!("File {} not found.", &tx_file);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
|
||||
Ok(command::ReceiveArgs {
|
||||
input: tx_file.to_owned(),
|
||||
message: message,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_finalize_args(args: &ArgMatches) -> Result<command::FinalizeArgs, ParseError> {
|
||||
let fluff = args.is_present("fluff");
|
||||
let tx_file = parse_required(args, "input")?;
|
||||
|
||||
if !Path::new(&tx_file).is_file() {
|
||||
let msg = format!("File {} not found.", tx_file);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::FinalizeArgs {
|
||||
input: tx_file.to_owned(),
|
||||
fluff: fluff,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_info_args(args: &ArgMatches) -> Result<command::InfoArgs, ParseError> {
|
||||
// minimum_confirmations
|
||||
let mc = parse_required(args, "minimum_confirmations")?;
|
||||
let mc = parse_u64(mc, "minimum_confirmations")?;
|
||||
Ok(command::InfoArgs {
|
||||
minimum_confirmations: mc,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, ParseError> {
|
||||
let tx_id = match args.value_of("id") {
|
||||
None => None,
|
||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
||||
};
|
||||
Ok(command::TxsArgs { id: tx_id })
|
||||
}
|
||||
|
||||
pub fn parse_repost_args(args: &ArgMatches) -> Result<command::RepostArgs, ParseError> {
|
||||
let tx_id = match args.value_of("id") {
|
||||
None => None,
|
||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
||||
};
|
||||
|
||||
let fluff = args.is_present("fluff");
|
||||
let dump_file = match args.value_of("dumpfile") {
|
||||
None => None,
|
||||
Some(d) => Some(d.to_owned()),
|
||||
};
|
||||
|
||||
Ok(command::RepostArgs {
|
||||
id: tx_id.unwrap(),
|
||||
dump_file: dump_file,
|
||||
fluff: fluff,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, ParseError> {
|
||||
let mut tx_id_string = "";
|
||||
let tx_id = match args.value_of("id") {
|
||||
None => None,
|
||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
||||
};
|
||||
let tx_slate_id = match args.value_of("txid") {
|
||||
None => None,
|
||||
Some(tx) => match tx.parse() {
|
||||
Ok(t) => {
|
||||
tx_id_string = tx;
|
||||
Some(t)
|
||||
}
|
||||
Err(e) => {
|
||||
let msg = format!("Could not parse txid parameter. e={}", e);
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
},
|
||||
};
|
||||
if (tx_id.is_none() && tx_slate_id.is_none()) || (tx_id.is_some() && tx_slate_id.is_some()) {
|
||||
let msg = format!("'id' (-i) or 'txid' (-t) argument is required.");
|
||||
return Err(ParseError::ArgumentError(msg));
|
||||
}
|
||||
Ok(command::CancelArgs {
|
||||
tx_id: tx_id,
|
||||
tx_slate_id: tx_slate_id,
|
||||
tx_id_string: tx_id_string.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn wallet_command(
|
||||
wallet_args: &ArgMatches,
|
||||
mut wallet_config: WalletConfig,
|
||||
mut node_client: impl NodeClient + 'static,
|
||||
) -> Result<String, Error> {
|
||||
if let Some(t) = wallet_config.chain_type.clone() {
|
||||
core::global::set_mining_mode(t);
|
||||
}
|
||||
|
||||
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("data_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 global_wallet_args = arg_parse!(parse_global_args(&wallet_config, &wallet_args));
|
||||
|
||||
node_client.set_node_url(&wallet_config.check_node_api_http_addr);
|
||||
node_client.set_node_api_secret(global_wallet_args.node_api_secret.clone());
|
||||
|
||||
// closure to instantiate wallet as needed by each subcommand
|
||||
let inst_wallet = || {
|
||||
let res = inst_wallet(wallet_config.clone(), &global_wallet_args, node_client);
|
||||
res.unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
std::process::exit(1);
|
||||
})
|
||||
};
|
||||
|
||||
let res = match wallet_args.subcommand() {
|
||||
("init", Some(args)) => {
|
||||
let a = arg_parse!(parse_init_args(&wallet_config, &global_wallet_args, &args));
|
||||
command::init(&global_wallet_args, a)
|
||||
}
|
||||
("recover", Some(args)) => {
|
||||
let a = arg_parse!(parse_recover_args(
|
||||
&wallet_config,
|
||||
&global_wallet_args,
|
||||
&args
|
||||
));
|
||||
command::recover(&wallet_config, a)
|
||||
}
|
||||
("listen", Some(args)) => {
|
||||
let mut c = wallet_config.clone();
|
||||
let mut g = global_wallet_args.clone();
|
||||
let a = arg_parse!(parse_listen_args(&mut c, &mut g, &args));
|
||||
command::listen(&wallet_config, &a, &g)
|
||||
}
|
||||
("owner_api", Some(_)) => {
|
||||
let mut g = global_wallet_args.clone();
|
||||
g.tls_conf = None;
|
||||
command::owner_api(inst_wallet(), &wallet_config, &g)
|
||||
}
|
||||
("web", Some(_)) => command::owner_api(inst_wallet(), &wallet_config, &global_wallet_args),
|
||||
("account", Some(args)) => {
|
||||
let a = arg_parse!(parse_account_args(&args));
|
||||
command::account(inst_wallet(), a)
|
||||
}
|
||||
("send", Some(args)) => {
|
||||
let a = arg_parse!(parse_send_args(&args));
|
||||
command::send(
|
||||
inst_wallet(),
|
||||
a,
|
||||
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||
)
|
||||
}
|
||||
("receive", Some(args)) => {
|
||||
let a = arg_parse!(parse_receive_args(&args));
|
||||
command::receive(inst_wallet(), &global_wallet_args, a)
|
||||
}
|
||||
("finalize", Some(args)) => {
|
||||
let a = arg_parse!(parse_finalize_args(&args));
|
||||
command::finalize(inst_wallet(), a)
|
||||
}
|
||||
("info", Some(args)) => {
|
||||
let a = arg_parse!(parse_info_args(&args));
|
||||
command::info(
|
||||
inst_wallet(),
|
||||
&global_wallet_args,
|
||||
a,
|
||||
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||
)
|
||||
}
|
||||
("outputs", Some(_)) => command::outputs(
|
||||
inst_wallet(),
|
||||
&global_wallet_args,
|
||||
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||
),
|
||||
("txs", Some(args)) => {
|
||||
let a = arg_parse!(parse_txs_args(&args));
|
||||
command::txs(
|
||||
inst_wallet(),
|
||||
&global_wallet_args,
|
||||
a,
|
||||
wallet_config.dark_background_color_scheme.unwrap_or(true),
|
||||
)
|
||||
}
|
||||
("repost", Some(args)) => {
|
||||
let a = arg_parse!(parse_repost_args(&args));
|
||||
command::repost(inst_wallet(), a)
|
||||
}
|
||||
("cancel", Some(args)) => {
|
||||
let a = arg_parse!(parse_cancel_args(&args));
|
||||
command::cancel(inst_wallet(), a)
|
||||
}
|
||||
("restore", Some(_)) => command::restore(inst_wallet()),
|
||||
("check", Some(_)) => command::check_repair(inst_wallet()),
|
||||
_ => {
|
||||
let msg = format!("Unknown wallet command, use 'grin help wallet' for details");
|
||||
return Err(ErrorKind::ArgumentError(msg).into());
|
||||
}
|
||||
};
|
||||
if let Err(e) = res {
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(wallet_args.subcommand().0.to_owned())
|
||||
}
|
||||
}
|
||||
@@ -1,528 +0,0 @@
|
||||
// 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.
|
||||
|
||||
//! Test wallet command line works as expected
|
||||
#[cfg(test)]
|
||||
mod wallet_tests {
|
||||
use clap;
|
||||
use grin_util as util;
|
||||
use grin_wallet;
|
||||
|
||||
use grin_wallet::test_framework::{self, LocalWalletClient, WalletProxy};
|
||||
|
||||
use clap::{App, ArgMatches};
|
||||
use grin_util::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::{env, fs};
|
||||
|
||||
use grin_config::GlobalWalletConfig;
|
||||
use grin_core::global;
|
||||
use grin_core::global::ChainTypes;
|
||||
use grin_keychain::ExtKeychain;
|
||||
use grin_wallet::{LMDBBackend, WalletBackend, WalletConfig, WalletInst, WalletSeed};
|
||||
|
||||
use super::super::wallet_args;
|
||||
|
||||
fn clean_output_dir(test_dir: &str) {
|
||||
let _ = fs::remove_dir_all(test_dir);
|
||||
}
|
||||
|
||||
fn setup(test_dir: &str) {
|
||||
util::init_test_logger();
|
||||
clean_output_dir(test_dir);
|
||||
global::set_mining_mode(ChainTypes::AutomatedTesting);
|
||||
}
|
||||
|
||||
/// Create a wallet config file in the given current directory
|
||||
pub fn config_command_wallet(
|
||||
dir_name: &str,
|
||||
wallet_name: &str,
|
||||
) -> Result<(), grin_wallet::Error> {
|
||||
let mut current_dir;
|
||||
let mut default_config = GlobalWalletConfig::default();
|
||||
current_dir = env::current_dir().unwrap_or_else(|e| {
|
||||
panic!("Error creating config file: {}", e);
|
||||
});
|
||||
current_dir.push(dir_name);
|
||||
current_dir.push(wallet_name);
|
||||
let _ = fs::create_dir_all(current_dir.clone());
|
||||
let mut config_file_name = current_dir.clone();
|
||||
config_file_name.push("grin-wallet.toml");
|
||||
if config_file_name.exists() {
|
||||
return Err(grin_wallet::ErrorKind::ArgumentError(
|
||||
"grin-wallet.toml already exists in the target directory. Please remove it first"
|
||||
.to_owned(),
|
||||
))?;
|
||||
}
|
||||
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(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles setup and detection of paths for wallet
|
||||
pub fn initial_setup_wallet(dir_name: &str, wallet_name: &str) -> WalletConfig {
|
||||
let mut current_dir;
|
||||
current_dir = env::current_dir().unwrap_or_else(|e| {
|
||||
panic!("Error creating config file: {}", e);
|
||||
});
|
||||
current_dir.push(dir_name);
|
||||
current_dir.push(wallet_name);
|
||||
let _ = fs::create_dir_all(current_dir.clone());
|
||||
let mut config_file_name = current_dir.clone();
|
||||
config_file_name.push("grin-wallet.toml");
|
||||
GlobalWalletConfig::new(config_file_name.to_str().unwrap())
|
||||
.unwrap()
|
||||
.members
|
||||
.unwrap()
|
||||
.wallet
|
||||
}
|
||||
|
||||
fn get_wallet_subcommand<'a>(
|
||||
wallet_dir: &str,
|
||||
wallet_name: &str,
|
||||
args: ArgMatches<'a>,
|
||||
) -> ArgMatches<'a> {
|
||||
match args.subcommand() {
|
||||
("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") {
|
||||
let _ = config_command_wallet(wallet_dir, wallet_name);
|
||||
}
|
||||
}
|
||||
wallet_args.to_owned()
|
||||
}
|
||||
_ => ArgMatches::new(),
|
||||
}
|
||||
}
|
||||
//
|
||||
// Helper to create an instance of the LMDB wallet
|
||||
fn instantiate_wallet(
|
||||
mut wallet_config: WalletConfig,
|
||||
node_client: LocalWalletClient,
|
||||
passphrase: &str,
|
||||
account: &str,
|
||||
) -> Result<Arc<Mutex<WalletInst<LocalWalletClient, ExtKeychain>>>, grin_wallet::Error> {
|
||||
wallet_config.chain_type = None;
|
||||
// First test decryption, so we can abort early if we have the wrong password
|
||||
let _ = WalletSeed::from_file(&wallet_config, passphrase)?;
|
||||
let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, node_client)?;
|
||||
db_wallet.set_parent_key_id_by_name(account)?;
|
||||
info!("Using LMDB Backend for wallet");
|
||||
Ok(Arc::new(Mutex::new(db_wallet)))
|
||||
}
|
||||
|
||||
fn execute_command(
|
||||
app: &App,
|
||||
test_dir: &str,
|
||||
wallet_name: &str,
|
||||
client: &LocalWalletClient,
|
||||
arg_vec: Vec<&str>,
|
||||
) -> Result<String, grin_wallet::Error> {
|
||||
let args = app.clone().get_matches_from(arg_vec);
|
||||
let args = get_wallet_subcommand(test_dir, wallet_name, args.clone());
|
||||
let mut config = initial_setup_wallet(test_dir, wallet_name);
|
||||
//unset chain type so it doesn't get reset
|
||||
config.chain_type = None;
|
||||
wallet_args::wallet_command(&args, config.clone(), client.clone())
|
||||
}
|
||||
|
||||
/// command line tests
|
||||
fn command_line_test_impl(test_dir: &str) -> Result<(), grin_wallet::Error> {
|
||||
setup(test_dir);
|
||||
// Create a new proxy to simulate server and wallet responses
|
||||
let mut wallet_proxy: WalletProxy<LocalWalletClient, ExtKeychain> =
|
||||
WalletProxy::new(test_dir);
|
||||
let chain = wallet_proxy.chain.clone();
|
||||
|
||||
// load app yaml. If it don't exist, just say so and exit
|
||||
let yml = load_yaml!("../grin.yml");
|
||||
let app = App::from_yaml(yml);
|
||||
|
||||
// wallet init
|
||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "init", "-h"];
|
||||
// should create new wallet file
|
||||
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?;
|
||||
|
||||
// trying to init twice - should fail
|
||||
assert!(execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone()).is_err());
|
||||
let client1 = LocalWalletClient::new("wallet1", wallet_proxy.tx.clone());
|
||||
|
||||
// add wallet to proxy
|
||||
//let wallet1 = test_framework::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
|
||||
let config1 = initial_setup_wallet(test_dir, "wallet1");
|
||||
let wallet1 = instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||
wallet_proxy.add_wallet("wallet1", client1.get_send_instance(), wallet1.clone());
|
||||
|
||||
// Create wallet 2
|
||||
let client2 = LocalWalletClient::new("wallet2", wallet_proxy.tx.clone());
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
||||
|
||||
let config2 = initial_setup_wallet(test_dir, "wallet2");
|
||||
let wallet2 = instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?;
|
||||
wallet_proxy.add_wallet("wallet2", client2.get_send_instance(), wallet2.clone());
|
||||
|
||||
// Set the wallet proxy listener running
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = wallet_proxy.run() {
|
||||
error!("Wallet Proxy error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Create some accounts in wallet 1
|
||||
let arg_vec = vec![
|
||||
"grin", "wallet", "-p", "password", "account", "-c", "mining",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"account",
|
||||
"-c",
|
||||
"account_1",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// Create some accounts in wallet 2
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"account",
|
||||
"-c",
|
||||
"account_1",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
||||
// already exists
|
||||
assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err());
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"account",
|
||||
"-c",
|
||||
"account_2",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||
|
||||
// let's see those accounts
|
||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "account"];
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||
|
||||
// let's see those accounts
|
||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "account"];
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
|
||||
|
||||
// Mine a bit into wallet 1 so we have something to send
|
||||
// (TODO: Be able to stop listeners so we can test this better)
|
||||
let wallet1 = instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||
grin_wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||
api.set_active_account("mining")?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let mut bh = 10u64;
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
|
||||
|
||||
let very_long_message = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef\
|
||||
This part should all be truncated";
|
||||
|
||||
// Update info and check
|
||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "info"];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// try a file exchange
|
||||
let file_name = format!("{}/tx1.part_tx", test_dir);
|
||||
let response_file_name = format!("{}/tx1.part_tx.response", test_dir);
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"mining",
|
||||
"send",
|
||||
"-m",
|
||||
"file",
|
||||
"-d",
|
||||
&file_name,
|
||||
"-g",
|
||||
very_long_message,
|
||||
"10",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"account_1",
|
||||
"receive",
|
||||
"-i",
|
||||
&file_name,
|
||||
"-g",
|
||||
"Thanks, Yeast!",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet2", &client2, arg_vec.clone())?;
|
||||
|
||||
// shouldn't be allowed to receive twice
|
||||
assert!(execute_command(&app, test_dir, "wallet2", &client2, arg_vec).is_err());
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"finalize",
|
||||
"-i",
|
||||
&response_file_name,
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
bh += 1;
|
||||
|
||||
let wallet1 = instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||
|
||||
// Check our transaction log, should have 10 entries
|
||||
grin_wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||
api.set_active_account("mining")?;
|
||||
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), bh as usize);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let _ = test_framework::award_blocks_to_wallet(&chain, wallet1.clone(), 10);
|
||||
bh += 10;
|
||||
|
||||
// update info for each
|
||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "info"];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"account_1",
|
||||
"info",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet2", &client1, arg_vec)?;
|
||||
|
||||
// check results in wallet 2
|
||||
let wallet2 = instantiate_wallet(config2.clone(), client2.clone(), "password", "default")?;
|
||||
grin_wallet::controller::owner_single_use(wallet2.clone(), |api| {
|
||||
api.set_active_account("account_1")?;
|
||||
let (_, wallet1_info) = api.retrieve_summary_info(true, 1)?;
|
||||
assert_eq!(wallet1_info.last_confirmed_height, bh);
|
||||
assert_eq!(wallet1_info.amount_currently_spendable, 10_000_000_000);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Self-send to same account, using smallest strategy
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"mining",
|
||||
"send",
|
||||
"-m",
|
||||
"file",
|
||||
"-d",
|
||||
&file_name,
|
||||
"-g",
|
||||
"Love, Yeast, Smallest",
|
||||
"-s",
|
||||
"smallest",
|
||||
"10",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"mining",
|
||||
"receive",
|
||||
"-i",
|
||||
&file_name,
|
||||
"-g",
|
||||
"Thanks, Yeast!",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec.clone())?;
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"finalize",
|
||||
"-i",
|
||||
&response_file_name,
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
bh += 1;
|
||||
|
||||
// Check our transaction log, should have bh entries + one for the self receive
|
||||
let wallet1 = instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||
|
||||
grin_wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||
api.set_active_account("mining")?;
|
||||
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), bh as usize + 1);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Try using the self-send method, splitting up outputs for the fun of it
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"mining",
|
||||
"send",
|
||||
"-m",
|
||||
"self",
|
||||
"-d",
|
||||
"mining",
|
||||
"-g",
|
||||
"Self love",
|
||||
"-o",
|
||||
"3",
|
||||
"-s",
|
||||
"smallest",
|
||||
"10",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
bh += 1;
|
||||
|
||||
// Check our transaction log, should have bh entries + 2 for the self receives
|
||||
let wallet1 = instantiate_wallet(config1.clone(), client1.clone(), "password", "default")?;
|
||||
|
||||
grin_wallet::controller::owner_single_use(wallet1.clone(), |api| {
|
||||
api.set_active_account("mining")?;
|
||||
let (refreshed, txs) = api.retrieve_txs(true, None, None)?;
|
||||
assert!(refreshed);
|
||||
assert_eq!(txs.len(), bh as usize + 2);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Another file exchange, don't send, but unlock with repair command
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"mining",
|
||||
"send",
|
||||
"-m",
|
||||
"file",
|
||||
"-d",
|
||||
&file_name,
|
||||
"-g",
|
||||
"Ain't sending",
|
||||
"10",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "check"];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// Another file exchange, cancel this time
|
||||
let arg_vec = vec![
|
||||
"grin",
|
||||
"wallet",
|
||||
"-p",
|
||||
"password",
|
||||
"-a",
|
||||
"mining",
|
||||
"send",
|
||||
"-m",
|
||||
"file",
|
||||
"-d",
|
||||
&file_name,
|
||||
"-g",
|
||||
"Ain't sending 2",
|
||||
"10",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
let arg_vec = vec![
|
||||
"grin", "wallet", "-p", "password", "-a", "mining", "cancel", "-i", "26",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// txs and outputs (mostly spit out for a visual in test logs)
|
||||
let arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "txs"];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// message output (mostly spit out for a visual in test logs)
|
||||
let arg_vec = vec![
|
||||
"grin", "wallet", "-p", "password", "-a", "mining", "txs", "-i", "10",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// txs and outputs (mostly spit out for a visual in test logs)
|
||||
let arg_vec = vec![
|
||||
"grin", "wallet", "-p", "password", "-a", "mining", "outputs",
|
||||
];
|
||||
execute_command(&app, test_dir, "wallet1", &client1, arg_vec)?;
|
||||
|
||||
// let logging finish
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wallet_command_line() {
|
||||
let test_dir = "target/test_output/command_line";
|
||||
if let Err(e) = command_line_test_impl(test_dir) {
|
||||
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-37
@@ -19,7 +19,7 @@ extern crate clap;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use crate::config::config::{SERVER_CONFIG_FILE_NAME, WALLET_CONFIG_FILE_NAME};
|
||||
use crate::config::config::SERVER_CONFIG_FILE_NAME;
|
||||
use crate::core::global;
|
||||
use crate::util::init_logger;
|
||||
use clap::App;
|
||||
@@ -29,7 +29,6 @@ use grin_core as core;
|
||||
use grin_p2p as p2p;
|
||||
use grin_servers as servers;
|
||||
use grin_util as util;
|
||||
use std::process::exit;
|
||||
|
||||
mod cmd;
|
||||
pub mod tui;
|
||||
@@ -72,8 +71,7 @@ fn main() {
|
||||
fn real_main() -> i32 {
|
||||
let yml = load_yaml!("grin.yml");
|
||||
let args = App::from_yaml(yml).get_matches();
|
||||
let mut wallet_config = None;
|
||||
let mut node_config = None;
|
||||
let node_config;
|
||||
|
||||
let chain_type = if args.is_present("floonet") {
|
||||
global::ChainTypes::Floonet
|
||||
@@ -92,41 +90,11 @@ fn real_main() -> i32 {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
("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(&chain_type, WALLET_CONFIG_FILE_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Load relevant config
|
||||
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(&chain_type).unwrap_or_else(|e| {
|
||||
panic!("Error loading wallet configuration: {}", e);
|
||||
});
|
||||
if !cmd::seed_exists(w.members.as_ref().unwrap().wallet.clone()) {
|
||||
if "init" == wallet_args.subcommand().0 || "recover" == wallet_args.subcommand().0 {
|
||||
} else {
|
||||
println!("Wallet seed file doesn't exist. Run `grin wallet init` first");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
let mut l = w.members.as_mut().unwrap().logging.clone().unwrap();
|
||||
l.tui_running = Some(false);
|
||||
init_logger(Some(l));
|
||||
info!(
|
||||
"Using wallet configuration file at {}",
|
||||
w.config_file_path.as_ref().unwrap().to_str().unwrap()
|
||||
);
|
||||
wallet_config = Some(w);
|
||||
}
|
||||
// When the subscommand is 'server' take into account the 'config_file' flag
|
||||
("server", Some(server_args)) => {
|
||||
if let Some(_path) = server_args.value_of("config_file") {
|
||||
@@ -184,9 +152,6 @@ fn real_main() -> i32 {
|
||||
// client commands and options
|
||||
("client", Some(client_args)) => cmd::client_command(client_args, node_config.unwrap()),
|
||||
|
||||
// client commands and options
|
||||
("wallet", Some(wallet_args)) => 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
|
||||
|
||||
@@ -71,233 +71,3 @@ subcommands:
|
||||
long: peer
|
||||
required: true
|
||||
takes_value: true
|
||||
- wallet:
|
||||
about: Wallet software for Grin
|
||||
args:
|
||||
- pass:
|
||||
help: Wallet passphrase used to encrypt wallet seed
|
||||
short: p
|
||||
long: pass
|
||||
takes_value: true
|
||||
- account:
|
||||
help: Wallet account to use for this operation
|
||||
short: a
|
||||
long: account
|
||||
takes_value: true
|
||||
default_value: default
|
||||
- data_dir:
|
||||
help: Directory in which to store wallet files
|
||||
short: dd
|
||||
long: data_dir
|
||||
takes_value: true
|
||||
- external:
|
||||
help: Listen on 0.0.0.0 interface to allow external connections (default is 127.0.0.1)
|
||||
short: e
|
||||
long: external
|
||||
takes_value: false
|
||||
- show_spent:
|
||||
help: Show spent outputs on wallet output commands
|
||||
short: s
|
||||
long: show_spent
|
||||
takes_value: false
|
||||
- api_server_address:
|
||||
help: Api address of running node on which to check inputs and post transactions
|
||||
short: r
|
||||
long: api_server_address
|
||||
takes_value: true
|
||||
subcommands:
|
||||
- account:
|
||||
about: List wallet accounts or create a new account
|
||||
args:
|
||||
- create:
|
||||
help: Create a new wallet account with provided name
|
||||
short: c
|
||||
long: create
|
||||
takes_value: true
|
||||
- listen:
|
||||
about: Runs the wallet in listening mode waiting for transactions
|
||||
args:
|
||||
- port:
|
||||
help: Port on which to run the wallet listener
|
||||
short: l
|
||||
long: port
|
||||
takes_value: true
|
||||
- method:
|
||||
help: Which method to use for communication
|
||||
short: m
|
||||
long: method
|
||||
possible_values:
|
||||
- http
|
||||
- keybase
|
||||
default_value: http
|
||||
takes_value: true
|
||||
- owner_api:
|
||||
about: Runs the wallet's local web API
|
||||
# Turned off, for now
|
||||
# - web:
|
||||
# about: Runs the local web wallet which can be accessed through a browser
|
||||
- send:
|
||||
about: Builds a transaction to send coins and sends to the specified listener directly
|
||||
args:
|
||||
- amount:
|
||||
help: Number of coins to send with optional fraction, e.g. 12.423
|
||||
index: 1
|
||||
- minimum_confirmations:
|
||||
help: Minimum number of confirmations required for an output to be spendable
|
||||
short: c
|
||||
long: min_conf
|
||||
default_value: "10"
|
||||
takes_value: true
|
||||
- selection_strategy:
|
||||
help: Coin/Output selection strategy.
|
||||
short: s
|
||||
long: selection
|
||||
possible_values:
|
||||
- all
|
||||
- smallest
|
||||
default_value: all
|
||||
takes_value: true
|
||||
- estimate_selection_strategies:
|
||||
help: Estimates all possible Coin/Output selection strategies.
|
||||
short: e
|
||||
long: estimate-selection
|
||||
- change_outputs:
|
||||
help: Number of change outputs to generate (mainly for testing)
|
||||
short: o
|
||||
long: change_outputs
|
||||
default_value: "1"
|
||||
takes_value: true
|
||||
- method:
|
||||
help: Method for sending this transaction
|
||||
short: m
|
||||
long: method
|
||||
possible_values:
|
||||
- http
|
||||
- file
|
||||
- self
|
||||
- keybase
|
||||
default_value: http
|
||||
takes_value: true
|
||||
- dest:
|
||||
help: Send the transaction to the provided server (start with http://) or save as file.
|
||||
short: d
|
||||
long: dest
|
||||
takes_value: true
|
||||
- fluff:
|
||||
help: Fluff the transaction (ignore Dandelion relay protocol)
|
||||
short: f
|
||||
long: fluff
|
||||
- message:
|
||||
help: Optional participant message to include
|
||||
short: g
|
||||
long: message
|
||||
takes_value: true
|
||||
- stored_tx:
|
||||
help: If present, use the previously stored Unconfirmed transaction with given id
|
||||
short: t
|
||||
long: stored_tx
|
||||
takes_value: true
|
||||
- receive:
|
||||
about: Processes a transaction file to accept a transfer from a sender
|
||||
args:
|
||||
- message:
|
||||
help: Optional participant message to include
|
||||
short: g
|
||||
long: message
|
||||
takes_value: true
|
||||
- input:
|
||||
help: Partial transaction to process, expects the sender's transaction file.
|
||||
short: i
|
||||
long: input
|
||||
takes_value: true
|
||||
- finalize:
|
||||
about: Processes a receiver's transaction file to finalize a transfer.
|
||||
args:
|
||||
- input:
|
||||
help: Partial transaction to process, expects the receiver's transaction file.
|
||||
short: i
|
||||
long: input
|
||||
takes_value: true
|
||||
- fluff:
|
||||
help: Fluff the transaction (ignore Dandelion relay protocol)
|
||||
short: f
|
||||
long: fluff
|
||||
- outputs:
|
||||
about: Raw wallet output info (list of outputs)
|
||||
- txs:
|
||||
about: Display transaction information
|
||||
args:
|
||||
- id:
|
||||
help: If specified, display transaction with given Id and all associated Inputs/Outputs
|
||||
short: i
|
||||
long: id
|
||||
takes_value: true
|
||||
- repost:
|
||||
about: Reposts a stored, completed but unconfirmed transaction to the chain, or dumps it to a file
|
||||
args:
|
||||
- id:
|
||||
help: Transaction ID containing the stored completed transaction
|
||||
short: i
|
||||
long: id
|
||||
takes_value: true
|
||||
- dumpfile:
|
||||
help: File name to duMp the transaction to instead of posting
|
||||
short: m
|
||||
long: dumpfile
|
||||
takes_value: true
|
||||
- fluff:
|
||||
help: Fluff the transaction (ignore Dandelion relay protocol)
|
||||
short: f
|
||||
long: fluff
|
||||
- cancel:
|
||||
about: Cancels an previously created transaction, freeing previously locked outputs for use again
|
||||
args:
|
||||
- id:
|
||||
help: The ID of the transaction to cancel
|
||||
short: i
|
||||
long: id
|
||||
takes_value: true
|
||||
- txid:
|
||||
help: The TxID UUID of the transaction to cancel
|
||||
short: t
|
||||
long: txid
|
||||
takes_value: true
|
||||
- info:
|
||||
about: Basic wallet contents summary
|
||||
args:
|
||||
- minimum_confirmations:
|
||||
help: Minimum number of confirmations required for an output to be spendable
|
||||
short: c
|
||||
long: min_conf
|
||||
default_value: "10"
|
||||
takes_value: true
|
||||
- init:
|
||||
about: Initialize a new wallet seed file and database
|
||||
args:
|
||||
- here:
|
||||
help: Create wallet files in the current directory instead of the default ~/.grin directory
|
||||
short: h
|
||||
long: here
|
||||
takes_value: false
|
||||
- short_wordlist:
|
||||
help: Generate a 12-word recovery phrase/seed instead of default 24
|
||||
short: s
|
||||
long: short_wordlist
|
||||
takes_value: false
|
||||
- recover:
|
||||
help: Initialize new wallet using a recovery phrase
|
||||
short: r
|
||||
long: recover
|
||||
takes_value: false
|
||||
- recover:
|
||||
about: Recover a wallet.seed file from a recovery phrase (default) or displays a recovery phrase for an existing seed file
|
||||
args:
|
||||
- display:
|
||||
help: Display wallet recovery phrase
|
||||
short: d
|
||||
long: display
|
||||
takes_value: false
|
||||
- restore:
|
||||
about: Restores a wallet contents from a seed file
|
||||
- check:
|
||||
about: Checks a wallet's outputs against a live node, repairing and restoring missing outputs if required
|
||||
|
||||
+1
-100
@@ -16,20 +16,10 @@
|
||||
|
||||
use built;
|
||||
|
||||
use reqwest;
|
||||
|
||||
use flate2::read::GzDecoder;
|
||||
use std::env;
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::io::Read;
|
||||
use std::path::{self, Path, PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use tar::Archive;
|
||||
|
||||
const _WEB_WALLET_TAG: &str = "0.3.0.1";
|
||||
|
||||
fn main() {
|
||||
// Setting up git hooks in the project: rustfmt and so on.
|
||||
let git_hooks = format!(
|
||||
@@ -58,93 +48,4 @@ fn main() {
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
format!("{}{}", env::var("OUT_DIR").unwrap(), "/built.rs"),
|
||||
);
|
||||
|
||||
// NB: Removed for the time being
|
||||
/*let web_wallet_install = install_web_wallet();
|
||||
match web_wallet_install {
|
||||
Ok(true) => {}
|
||||
_ => println!(
|
||||
"WARNING : Web wallet could not be installed due to {:?}",
|
||||
web_wallet_install
|
||||
),
|
||||
}*/
|
||||
}
|
||||
|
||||
fn _download_and_decompress(target_file: &str) -> Result<bool, Box<std::error::Error>> {
|
||||
let req_path = format!("https://github.com/mimblewimble/grin-web-wallet/releases/download/{}/grin-web-wallet.tar.gz", _WEB_WALLET_TAG);
|
||||
let mut resp = reqwest::get(&req_path)?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// read response
|
||||
let mut out: Vec<u8> = vec![];
|
||||
resp.read_to_end(&mut out)?;
|
||||
|
||||
// Gunzip
|
||||
let mut d = GzDecoder::new(&out[..]);
|
||||
let mut decomp: Vec<u8> = vec![];
|
||||
d.read_to_end(&mut decomp)?;
|
||||
|
||||
// write temp file
|
||||
let mut buffer = File::create(target_file.clone())?;
|
||||
buffer.write_all(&decomp)?;
|
||||
buffer.flush()?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Download and unzip tagged web-wallet build
|
||||
fn _install_web_wallet() -> Result<bool, Box<std::error::Error>> {
|
||||
let target_file = format!(
|
||||
"{}/grin-web-wallet-{}.tar",
|
||||
env::var("OUT_DIR")?,
|
||||
_WEB_WALLET_TAG
|
||||
);
|
||||
let out_dir = env::var("OUT_DIR")?;
|
||||
let mut out_path = PathBuf::from(&out_dir);
|
||||
out_path.pop();
|
||||
out_path.pop();
|
||||
out_path.pop();
|
||||
|
||||
// only re-download if needed
|
||||
println!("{}", target_file);
|
||||
if !Path::new(&target_file).is_file() {
|
||||
let success = _download_and_decompress(&target_file)?;
|
||||
if !success {
|
||||
return Ok(false); // could not download and decompress
|
||||
}
|
||||
}
|
||||
|
||||
// remove old version
|
||||
let mut remove_path = out_path.clone();
|
||||
remove_path.push("grin-wallet");
|
||||
let _ = fs::remove_dir_all(remove_path);
|
||||
|
||||
// Untar
|
||||
let file = File::open(target_file)?;
|
||||
let mut a = Archive::new(file);
|
||||
|
||||
for file in a.entries()? {
|
||||
let mut file = file?;
|
||||
let h = file.header().clone();
|
||||
let path = h.path()?.clone().into_owned();
|
||||
let is_dir = path.to_str().unwrap().ends_with(path::MAIN_SEPARATOR);
|
||||
let path = path.strip_prefix("dist")?;
|
||||
let mut final_path = out_path.clone();
|
||||
final_path.push(path);
|
||||
|
||||
let mut tmp: Vec<u8> = vec![];
|
||||
file.read_to_end(&mut tmp)?;
|
||||
if is_dir {
|
||||
fs::create_dir_all(final_path)?;
|
||||
} else {
|
||||
let mut buffer = File::create(final_path)?;
|
||||
buffer.write_all(&tmp)?;
|
||||
buffer.flush()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user