Wallet command line automated testing (#2097)

* wallet test infrastructure

* rustfmt

* successful wallet create

* rustfmt

* start of command line tests

* rustfmt
This commit is contained in:
Yeastplume 2018-12-07 15:06:28 +00:00 committed by GitHub
parent 4573c0f1ee
commit d1b484259b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 500 additions and 190 deletions

1
Cargo.lock generated
View file

@ -956,6 +956,7 @@ dependencies = [
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
"grin_api 0.4.2",
"grin_chain 0.4.2",
"grin_config 0.4.2",
"grin_core 0.4.2",
"grin_keychain 0.4.2",
"grin_store 0.4.2",

View file

@ -14,27 +14,11 @@
use clap::ArgMatches;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use config::GlobalWalletConfig;
use core::global;
use grin_wallet::{self, command, command_args, WalletConfig, WalletSeed};
use grin_wallet::{self, command_args, HTTPNodeClient, WalletConfig, WalletSeed};
use servers::start_webwallet_server;
// define what to do on argument error
macro_rules! arg_parse {
( $r:expr ) => {
match $r {
Ok(res) => res,
Err(e) => {
println!("{}", e);
return 0;
}
}
};
}
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, password)
@ -55,115 +39,17 @@ pub fn seed_exists(wallet_config: WalletConfig) -> bool {
pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i32 {
// just get defaults from the global config
let mut wallet_config = config.members.unwrap().wallet;
let wallet_config = config.members.unwrap().wallet;
if let Some(t) = wallet_config.chain_type.clone() {
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("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!(command_args::parse_global_args(
&wallet_config,
&wallet_args
));
// closure to instantiate wallet as needed by each subcommand
let inst_wallet = || {
let res = command_args::inst_wallet(wallet_config.clone(), &global_wallet_args);
res.unwrap_or_else(|e| {
println!("{}", e);
std::process::exit(0);
})
// web wallet http server must be started from here
let _ = match wallet_args.subcommand() {
("web", Some(_)) => start_webwallet_server(),
_ => {}
};
let res = match wallet_args.subcommand() {
("init", Some(args)) => {
let a = arg_parse!(command_args::parse_init_args(&wallet_config, &args));
command::init(&global_wallet_args, a)
}
("recover", Some(args)) => {
let a = arg_parse!(command_args::parse_recover_args(&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!(command_args::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(), &g)
}
("web", Some(_)) => {
start_webwallet_server();
command::owner_api(inst_wallet(), &global_wallet_args)
}
("account", Some(args)) => {
let a = arg_parse!(command_args::parse_account_args(&args));
command::account(inst_wallet(), a)
}
("send", Some(args)) => {
let a = arg_parse!(command_args::parse_send_args(&args));
command::send(inst_wallet(), a)
}
("receive", Some(args)) => {
let a = arg_parse!(command_args::parse_receive_args(&args));
command::receive(inst_wallet(), &global_wallet_args, a)
}
("finalize", Some(args)) => {
let a = arg_parse!(command_args::parse_finalize_args(&args));
command::finalize(inst_wallet(), a)
}
("info", Some(args)) => {
let a = arg_parse!(command_args::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!(command_args::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!(command_args::parse_repost_args(&args));
command::repost(inst_wallet(), a)
}
("cancel", Some(args)) => {
let a = arg_parse!(command_args::parse_cancel_args(&args));
command::cancel(inst_wallet(), a)
}
("restore", Some(_)) => command::restore(inst_wallet()),
_ => {
println!("Unknown wallet command, use 'grin help wallet' for details");
return 0;
}
};
let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
let res = command_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));

View file

@ -10,7 +10,7 @@ workspace = '..'
[dependencies]
blake2-rfc = "0.2"
clap = "2.31"
clap = { version = "2.31", features = ["yaml"] }
rpassword = "2.0.0"
byteorder = "1"
failure = "0.1"
@ -41,3 +41,4 @@ grin_util = { path = "../util", version = "0.4.2" }
[dev-dependencies]
grin_chain = { path = "../chain", version = "0.4.2" }
grin_store = { path = "../store", version = "0.4.2" }
grin_config = { path = "../config", version = "0.4.2" }

View file

@ -20,7 +20,7 @@ use api;
use controller;
use core::libtx::slate::Slate;
use libwallet::{Error, ErrorKind};
use {instantiate_wallet, WalletCommAdapter, WalletConfig};
use {instantiate_wallet, HTTPNodeClient, WalletCommAdapter, WalletConfig};
#[derive(Clone)]
pub struct HTTPWalletCommAdapter {}
@ -70,9 +70,9 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
account: &str,
node_api_secret: Option<String>,
) -> Result<(), Error> {
let wallet =
instantiate_wallet(config.clone(), passphrase, account, node_api_secret.clone())
.context(ErrorKind::WalletSeedDecryption)?;
let node_client = HTTPNodeClient::new(&config.check_node_api_http_addr, node_api_secret);
let wallet = instantiate_wallet(config.clone(), node_client, passphrase, account)
.context(ErrorKind::WalletSeedDecryption)?;
let listen_addr = params.get("api_listen_addr").unwrap();
let tls_conf = match params.get("certificate") {
Some(s) => Some(api::TLSConfig::new(

View file

@ -25,7 +25,7 @@ use std::process::{Command, Stdio};
use std::str::from_utf8;
use std::thread::sleep;
use std::time::{Duration, Instant};
use {instantiate_wallet, WalletCommAdapter, WalletConfig};
use {instantiate_wallet, HTTPNodeClient, WalletCommAdapter, WalletConfig};
const TTL: u16 = 60; // TODO: Pass this as a parameter
const SLEEP_DURATION: Duration = Duration::from_millis(5000);
@ -219,9 +219,9 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
account: &str,
node_api_secret: Option<String>,
) -> Result<(), Error> {
let wallet =
instantiate_wallet(config.clone(), passphrase, account, node_api_secret.clone())
.context(ErrorKind::WalletSeedDecryption)?;
let node_client = HTTPNodeClient::new(&config.check_node_api_http_addr, node_api_secret);
let wallet = instantiate_wallet(config.clone(), node_client, passphrase, account)
.context(ErrorKind::WalletSeedDecryption)?;
println!("Listening for messages via keybase chat...");
loop {

View file

@ -32,11 +32,9 @@ use error::{Error, ErrorKind};
use {controller, display, HTTPNodeClient, WalletConfig, WalletInst, WalletSeed};
use {
FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter, LMDBBackend,
NullWalletCommAdapter,
NodeClient, NullWalletCommAdapter,
};
pub type WalletRef = Arc<Mutex<WalletInst<HTTPNodeClient, keychain::ExtKeychain>>>;
/// Arguments common to all wallet commands
#[derive(Clone)]
pub struct GlobalArgs {
@ -131,7 +129,10 @@ pub fn listen(config: &WalletConfig, args: &ListenArgs, g_args: &GlobalArgs) ->
Ok(())
}
pub fn owner_api(wallet: WalletRef, g_args: &GlobalArgs) -> Result<(), Error> {
pub fn owner_api(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
g_args: &GlobalArgs,
) -> Result<(), Error> {
let res = controller::owner_listener(
wallet,
"127.0.0.1:13420",
@ -149,7 +150,10 @@ pub struct AccountArgs {
pub create: Option<String>,
}
pub fn account(wallet: WalletRef, args: AccountArgs) -> Result<(), Error> {
pub fn account(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
args: AccountArgs,
) -> Result<(), Error> {
if args.create.is_none() {
let res = controller::owner_single_use(wallet, |api| {
let acct_mappings = api.accounts()?;
@ -192,7 +196,10 @@ pub struct SendArgs {
pub max_outputs: usize,
}
pub fn send(wallet: WalletRef, args: SendArgs) -> Result<(), Error> {
pub fn send(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
args: SendArgs,
) -> Result<(), Error> {
controller::owner_single_use(wallet.clone(), |api| {
let result = api.initiate_tx(
None,
@ -267,7 +274,11 @@ pub struct ReceiveArgs {
pub message: Option<String>,
}
pub fn receive(wallet: WalletRef, g_args: &GlobalArgs, args: ReceiveArgs) -> Result<(), Error> {
pub fn receive(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
g_args: &GlobalArgs,
args: ReceiveArgs,
) -> Result<(), Error> {
let adapter = FileWalletCommAdapter::new();
let mut slate = adapter.receive_tx_async(&args.input)?;
controller::foreign_single_use(wallet, |api| {
@ -289,7 +300,10 @@ pub struct FinalizeArgs {
pub fluff: bool,
}
pub fn finalize(wallet: WalletRef, args: FinalizeArgs) -> Result<(), Error> {
pub fn finalize(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
args: FinalizeArgs,
) -> Result<(), Error> {
let adapter = FileWalletCommAdapter::new();
let mut slate = adapter.receive_tx_async(&args.input)?;
controller::owner_single_use(wallet.clone(), |api| {
@ -320,7 +334,7 @@ pub struct InfoArgs {
}
pub fn info(
wallet: WalletRef,
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
g_args: &GlobalArgs,
args: InfoArgs,
dark_scheme: bool,
@ -334,7 +348,11 @@ pub fn info(
Ok(())
}
pub fn outputs(wallet: WalletRef, g_args: &GlobalArgs, dark_scheme: bool) -> Result<(), Error> {
pub fn outputs(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
g_args: &GlobalArgs,
dark_scheme: bool,
) -> Result<(), Error> {
controller::owner_single_use(wallet.clone(), |api| {
let (height, _) = api.node_height()?;
let (validated, outputs) = api.retrieve_outputs(g_args.show_spent, true, None)?;
@ -350,7 +368,7 @@ pub struct TxsArgs {
}
pub fn txs(
wallet: WalletRef,
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
g_args: &GlobalArgs,
args: TxsArgs,
dark_scheme: bool,
@ -385,7 +403,10 @@ pub struct RepostArgs {
pub fluff: bool,
}
pub fn repost(wallet: WalletRef, args: RepostArgs) -> Result<(), Error> {
pub fn repost(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
args: RepostArgs,
) -> Result<(), Error> {
controller::owner_single_use(wallet.clone(), |api| {
let (_, txs) = api.retrieve_txs(true, Some(args.id), None)?;
let stored_tx = txs[0].get_stored_tx();
@ -428,7 +449,10 @@ pub struct CancelArgs {
pub tx_id_string: String,
}
pub fn cancel(wallet: WalletRef, args: CancelArgs) -> Result<(), Error> {
pub fn cancel(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
args: CancelArgs,
) -> Result<(), Error> {
controller::owner_single_use(wallet.clone(), |api| {
let result = api.cancel_tx(args.tx_id, args.tx_slate_id);
match result {
@ -445,7 +469,9 @@ pub fn cancel(wallet: WalletRef, args: CancelArgs) -> Result<(), Error> {
Ok(())
}
pub fn restore(wallet: WalletRef) -> Result<(), Error> {
pub fn restore(
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
) -> Result<(), Error> {
controller::owner_single_use(wallet.clone(), |api| {
let result = api.restore();
match result {

View file

@ -14,19 +14,36 @@
/// Argument parsing and error handling for wallet commands
use clap::ArgMatches;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use util::Mutex;
use failure::Fail;
use api::TLSConfig;
use core::core;
use core;
use keychain;
use std::path::Path;
use util::file::get_first_line;
use ErrorKind;
use {command, instantiate_wallet, WalletConfig, WalletSeed};
use {command, instantiate_wallet, NodeClient, WalletConfig, WalletInst, WalletSeed};
use {Error, ErrorKind};
// 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 Error {
pub enum ParseError {
#[fail(display = "Invalid Arguments: {}", _0)]
ArgumentError(String),
}
@ -63,14 +80,10 @@ fn prompt_password_confirm() -> String {
pub fn inst_wallet(
config: WalletConfig,
g_args: &command::GlobalArgs,
) -> Result<command::WalletRef, Error> {
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(),
&passphrase,
&g_args.account,
g_args.node_api_secret.clone(),
);
let res = instantiate_wallet(config.clone(), node_client, &passphrase, &g_args.account);
match res {
Ok(p) => Ok(p),
Err(e) => {
@ -82,31 +95,31 @@ pub fn inst_wallet(
_ => format!("Error instantiating wallet: {}", e),
}
};
Err(Error::ArgumentError(msg))
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, Error> {
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(Error::ArgumentError(msg))
Err(ParseError::ArgumentError(msg))
}
}
}
// parses a number, or throws error with message otherwise
fn parse_u64(arg: &str, name: &str) -> Result<u64, Error> {
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(Error::ArgumentError(msg))
Err(ParseError::ArgumentError(msg))
}
}
}
@ -114,7 +127,7 @@ fn parse_u64(arg: &str, name: &str) -> Result<u64, Error> {
pub fn parse_global_args(
config: &WalletConfig,
args: &ArgMatches,
) -> Result<command::GlobalArgs, Error> {
) -> Result<command::GlobalArgs, ParseError> {
let account = parse_required(args, "account")?;
let mut show_spent = false;
if args.is_present("show_spent") {
@ -133,7 +146,7 @@ pub fn parse_global_args(
Some(k) => k,
None => {
let msg = format!("Private key for certificate is not set");
return Err(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
};
Some(TLSConfig::new(file, key))
@ -151,18 +164,22 @@ pub fn parse_global_args(
pub fn parse_init_args(
config: &WalletConfig,
g_args: &command::GlobalArgs,
args: &ArgMatches,
) -> Result<command::InitArgs, Error> {
) -> Result<command::InitArgs, ParseError> {
if let Err(e) = WalletSeed::seed_file_exists(config) {
let msg = format!("Not creating wallet - {}", e.inner);
return Err(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
let list_length = match args.is_present("short_wordlist") {
false => 32,
true => 16,
};
println!("Please enter a password for your new wallet");
let password = prompt_password_confirm();
let password = match g_args.password.clone() {
Some(p) => p,
None => prompt_password_confirm(),
};
Ok(command::InitArgs {
list_length: list_length,
password: password,
@ -173,14 +190,14 @@ pub fn parse_init_args(
pub fn parse_recover_args(
g_args: &command::GlobalArgs,
args: &ArgMatches,
) -> Result<command::RecoverArgs, Error> {
) -> Result<command::RecoverArgs, ParseError> {
let (passphrase, recovery_phrase) = {
match args.value_of("recovery_phrase") {
None => (prompt_password(&g_args.password), None),
Some(l) => {
if WalletSeed::from_mnemonic(l).is_err() {
let msg = format!("Recovery word phrase is invalid");
return Err(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
println!("Please provide a new password for the recovered wallet");
(prompt_password_confirm(), Some(l.to_owned()))
@ -197,7 +214,7 @@ pub fn parse_listen_args(
config: &mut WalletConfig,
g_args: &mut command::GlobalArgs,
args: &ArgMatches,
) -> Result<command::ListenArgs, Error> {
) -> Result<command::ListenArgs, ParseError> {
// listen args
let pass = match g_args.password.clone() {
Some(p) => Some(p.to_owned()),
@ -213,7 +230,7 @@ pub fn parse_listen_args(
})
}
pub fn parse_account_args(account_args: &ArgMatches) -> Result<command::AccountArgs, Error> {
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()),
@ -221,10 +238,10 @@ pub fn parse_account_args(account_args: &ArgMatches) -> Result<command::AccountA
Ok(command::AccountArgs { create: create })
}
pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, Error> {
pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, ParseError> {
// amount
let amount = parse_required(args, "amount")?;
let amount = core::amount_from_hr_string(amount);
let amount = core::core::amount_from_hr_string(amount);
let amount = match amount {
Ok(a) => a,
Err(e) => {
@ -232,7 +249,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, Error> {
"Could not parse amount as a number with optional decimal point. e={}",
e
);
return Err(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
};
@ -268,7 +285,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, Error> {
"HTTP Destination should start with http://: or https://: {}",
dest,
);
return Err(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
// change_outputs
@ -294,7 +311,7 @@ pub fn parse_send_args(args: &ArgMatches) -> Result<command::SendArgs, Error> {
})
}
pub fn parse_receive_args(receive_args: &ArgMatches) -> Result<command::ReceiveArgs, Error> {
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()),
@ -307,7 +324,7 @@ pub fn parse_receive_args(receive_args: &ArgMatches) -> Result<command::ReceiveA
// validate input
if !Path::new(&tx_file).is_file() {
let msg = format!("File {} not found.", &tx_file);
return Err(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
Ok(command::ReceiveArgs {
@ -316,13 +333,13 @@ pub fn parse_receive_args(receive_args: &ArgMatches) -> Result<command::ReceiveA
})
}
pub fn parse_finalize_args(args: &ArgMatches) -> Result<command::FinalizeArgs, Error> {
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(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
Ok(command::FinalizeArgs {
input: tx_file.to_owned(),
@ -330,7 +347,7 @@ pub fn parse_finalize_args(args: &ArgMatches) -> Result<command::FinalizeArgs, E
})
}
pub fn parse_info_args(args: &ArgMatches) -> Result<command::InfoArgs, Error> {
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")?;
@ -339,7 +356,7 @@ pub fn parse_info_args(args: &ArgMatches) -> Result<command::InfoArgs, Error> {
})
}
pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, Error> {
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),
@ -347,7 +364,7 @@ pub fn parse_txs_args(args: &ArgMatches) -> Result<command::TxsArgs, Error> {
Ok(command::TxsArgs { id: tx_id })
}
pub fn parse_repost_args(args: &ArgMatches) -> Result<command::RepostArgs, Error> {
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),
@ -366,7 +383,7 @@ pub fn parse_repost_args(args: &ArgMatches) -> Result<command::RepostArgs, Error
})
}
pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, Error> {
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,
@ -381,13 +398,13 @@ pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, Error
}
Err(e) => {
let msg = format!("Could not parse txid parameter. e={}", e);
return Err(Error::ArgumentError(msg));
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(Error::ArgumentError(msg));
return Err(ParseError::ArgumentError(msg));
}
Ok(command::CancelArgs {
tx_id: tx_id,
@ -395,3 +412,119 @@ pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, Error
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("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(0);
})
};
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(&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(), &g)
}
("web", Some(_)) => command::owner_api(inst_wallet(), &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)
}
("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()),
_ => {
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())
}
}

View file

@ -96,6 +96,10 @@ pub enum ErrorKind {
#[fail(display = "BIP39 Mnemonic (word list) Error")]
Mnemonic,
/// Command line argument error
#[fail(display = "{}", _0)]
ArgumentError(String),
/// Other
#[fail(display = "Generic error: {}", _0)]
GenericError(String),

View file

@ -76,14 +76,13 @@ use util::Mutex;
/// Helper to create an instance of the LMDB wallet
pub fn instantiate_wallet(
wallet_config: WalletConfig,
node_client: impl NodeClient + 'static,
passphrase: &str,
account: &str,
node_api_secret: Option<String>,
) -> Result<Arc<Mutex<WalletInst<HTTPNodeClient, keychain::ExtKeychain>>>, Error> {
) -> Result<Arc<Mutex<WalletInst<impl NodeClient, keychain::ExtKeychain>>>, Error> {
// First test decryption, so we can abort early if we have the wrong password
let _ = WalletSeed::from_file(&wallet_config, passphrase)?;
let client_n = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, node_api_secret);
let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, client_n)?;
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)))

View file

@ -188,9 +188,15 @@ pub trait NodeClient: Sync + Send + Clone {
/// Return the URL of the check node
fn node_url(&self) -> &str;
/// Set the node URL
fn set_node_url(&mut self, node_url: &str);
/// Return the node api secret
fn node_api_secret(&self) -> Option<String>;
/// Change the API secret
fn set_node_api_secret(&mut self, node_api_secret: Option<String>);
/// Posts a transaction to a grin node
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), Error>;

View file

@ -52,6 +52,14 @@ impl NodeClient for HTTPNodeClient {
self.node_api_secret.clone()
}
fn set_node_url(&mut self, node_url: &str) {
self.node_url = node_url.to_owned();
}
fn set_node_api_secret(&mut self, node_api_secret: Option<String>) {
self.node_api_secret = node_api_secret;
}
/// Posts a transaction to a grin node
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> {
let url;

View file

@ -0,0 +1,245 @@
// 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
extern crate grin_chain as chain;
extern crate grin_config as config;
extern crate grin_core as core;
extern crate grin_keychain as keychain;
extern crate grin_store as store;
extern crate grin_util as util;
extern crate grin_wallet as wallet;
extern crate rand;
#[macro_use]
extern crate log;
extern crate chrono;
extern crate serde;
extern crate uuid;
#[macro_use]
extern crate clap;
mod common;
use common::testclient::{LocalWalletClient, WalletProxy};
use clap::{App, ArgMatches};
use std::thread;
use std::time::Duration;
use std::{env, fs};
use config::GlobalWalletConfig;
use core::global;
use core::global::ChainTypes;
use keychain::ExtKeychain;
use wallet::{command_args, WalletConfig};
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<(), 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(wallet::ErrorKind::ArgumentError(
"grin-wallet.toml already exists in the target directory. Please remove it first"
.to_owned(),
))?;
}
default_config.update_paths(&current_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(),
}
}
fn execute_command(
app: &App,
test_dir: &str,
wallet_name: &str,
client: &LocalWalletClient,
arg_vec: Vec<&str>,
) -> Result<String, wallet::Error> {
let args = app.clone().get_matches_from(arg_vec);
let args = get_wallet_subcommand(test_dir, wallet_name, args.clone());
let config = initial_setup_wallet(test_dir, wallet_name);
command_args::wallet_command(&args, config.clone(), client.clone())
}
/// self send impl
fn command_line_test_impl(test_dir: &str) -> Result<(), 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);
// load app yaml. If it don't exist, just say so and exit
let yml = load_yaml!("../../src/bin/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());
// add wallet to proxy
let wallet1 = common::create_wallet(&format!("{}/wallet1", test_dir), client1.clone());
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 wallet2 = common::create_wallet(&format!("{}/wallet2", test_dir), client2.clone());
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)?;
// Mine a bit into wallet 1 so we have something to send
//let mut bh = 10u64;
//let chain = wallet_proxy.chain.clone();
//let _ = common::award_blocks_to_wallet(&chain, wallet1.clone(), bh as usize);
// let's see those accounts
let arg_vec = vec!["grin", "wallet", "-p", "password", "account"];
execute_command(&app, test_dir, "wallet2", &client2, arg_vec)?;
// Start wallet 1's listener, collect some coinbase outputs
let _arg_vec = vec!["grin", "wallet", "-p", "password", "-a", "mining", "listen"];
//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 = "test_output/command_line";
if let Err(e) = command_line_test_impl(test_dir) {
panic!("Libwallet Error: {} - {}", e, e.backtrace().unwrap());
}
}

View file

@ -393,7 +393,8 @@ impl NodeClient for LocalWalletClient {
fn node_api_secret(&self) -> Option<String> {
None
}
fn set_node_url(&mut self, _node_url: &str) {}
fn set_node_api_secret(&mut self, _node_api_secret: Option<String>) {}
/// Posts a transaction to a grin node
/// In this case it will create a new block with award rewarded to
fn post_tx(&self, tx: &TxWrapper, _fluff: bool) -> Result<(), libwallet::Error> {