mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-01 17:01:09 +03:00
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:
parent
4573c0f1ee
commit
d1b484259b
13 changed files with 500 additions and 190 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -956,6 +956,7 @@ dependencies = [
|
||||||
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"grin_api 0.4.2",
|
"grin_api 0.4.2",
|
||||||
"grin_chain 0.4.2",
|
"grin_chain 0.4.2",
|
||||||
|
"grin_config 0.4.2",
|
||||||
"grin_core 0.4.2",
|
"grin_core 0.4.2",
|
||||||
"grin_keychain 0.4.2",
|
"grin_keychain 0.4.2",
|
||||||
"grin_store 0.4.2",
|
"grin_store 0.4.2",
|
||||||
|
|
|
@ -14,27 +14,11 @@
|
||||||
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use config::GlobalWalletConfig;
|
use config::GlobalWalletConfig;
|
||||||
use core::global;
|
use grin_wallet::{self, command_args, HTTPNodeClient, WalletConfig, WalletSeed};
|
||||||
use grin_wallet::{self, command, command_args, WalletConfig, WalletSeed};
|
|
||||||
use servers::start_webwallet_server;
|
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) {
|
pub fn _init_wallet_seed(wallet_config: WalletConfig, password: &str) {
|
||||||
if let Err(_) = WalletSeed::from_file(&wallet_config, password) {
|
if let Err(_) = WalletSeed::from_file(&wallet_config, password) {
|
||||||
WalletSeed::init_file(&wallet_config, 32, 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 {
|
pub fn wallet_command(wallet_args: &ArgMatches, config: GlobalWalletConfig) -> i32 {
|
||||||
// just get defaults from the global config
|
// 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() {
|
// web wallet http server must be started from here
|
||||||
global::set_mining_mode(t);
|
let _ = match wallet_args.subcommand() {
|
||||||
}
|
("web", Some(_)) => start_webwallet_server(),
|
||||||
|
_ => {}
|
||||||
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);
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = match wallet_args.subcommand() {
|
let node_client = HTTPNodeClient::new(&wallet_config.check_node_api_http_addr, None);
|
||||||
("init", Some(args)) => {
|
let res = command_args::wallet_command(wallet_args, wallet_config, node_client);
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// we need to give log output a chance to catch up before exiting
|
// we need to give log output a chance to catch up before exiting
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ workspace = '..'
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
blake2-rfc = "0.2"
|
blake2-rfc = "0.2"
|
||||||
clap = "2.31"
|
clap = { version = "2.31", features = ["yaml"] }
|
||||||
rpassword = "2.0.0"
|
rpassword = "2.0.0"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
|
@ -41,3 +41,4 @@ grin_util = { path = "../util", version = "0.4.2" }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
grin_chain = { path = "../chain", version = "0.4.2" }
|
grin_chain = { path = "../chain", version = "0.4.2" }
|
||||||
grin_store = { path = "../store", version = "0.4.2" }
|
grin_store = { path = "../store", version = "0.4.2" }
|
||||||
|
grin_config = { path = "../config", version = "0.4.2" }
|
||||||
|
|
|
@ -20,7 +20,7 @@ use api;
|
||||||
use controller;
|
use controller;
|
||||||
use core::libtx::slate::Slate;
|
use core::libtx::slate::Slate;
|
||||||
use libwallet::{Error, ErrorKind};
|
use libwallet::{Error, ErrorKind};
|
||||||
use {instantiate_wallet, WalletCommAdapter, WalletConfig};
|
use {instantiate_wallet, HTTPNodeClient, WalletCommAdapter, WalletConfig};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HTTPWalletCommAdapter {}
|
pub struct HTTPWalletCommAdapter {}
|
||||||
|
@ -70,9 +70,9 @@ impl WalletCommAdapter for HTTPWalletCommAdapter {
|
||||||
account: &str,
|
account: &str,
|
||||||
node_api_secret: Option<String>,
|
node_api_secret: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let wallet =
|
let node_client = HTTPNodeClient::new(&config.check_node_api_http_addr, node_api_secret);
|
||||||
instantiate_wallet(config.clone(), passphrase, account, node_api_secret.clone())
|
let wallet = instantiate_wallet(config.clone(), node_client, passphrase, account)
|
||||||
.context(ErrorKind::WalletSeedDecryption)?;
|
.context(ErrorKind::WalletSeedDecryption)?;
|
||||||
let listen_addr = params.get("api_listen_addr").unwrap();
|
let listen_addr = params.get("api_listen_addr").unwrap();
|
||||||
let tls_conf = match params.get("certificate") {
|
let tls_conf = match params.get("certificate") {
|
||||||
Some(s) => Some(api::TLSConfig::new(
|
Some(s) => Some(api::TLSConfig::new(
|
||||||
|
|
|
@ -25,7 +25,7 @@ use std::process::{Command, Stdio};
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
use std::time::{Duration, Instant};
|
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 TTL: u16 = 60; // TODO: Pass this as a parameter
|
||||||
const SLEEP_DURATION: Duration = Duration::from_millis(5000);
|
const SLEEP_DURATION: Duration = Duration::from_millis(5000);
|
||||||
|
@ -219,9 +219,9 @@ impl WalletCommAdapter for KeybaseWalletCommAdapter {
|
||||||
account: &str,
|
account: &str,
|
||||||
node_api_secret: Option<String>,
|
node_api_secret: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let wallet =
|
let node_client = HTTPNodeClient::new(&config.check_node_api_http_addr, node_api_secret);
|
||||||
instantiate_wallet(config.clone(), passphrase, account, node_api_secret.clone())
|
let wallet = instantiate_wallet(config.clone(), node_client, passphrase, account)
|
||||||
.context(ErrorKind::WalletSeedDecryption)?;
|
.context(ErrorKind::WalletSeedDecryption)?;
|
||||||
|
|
||||||
println!("Listening for messages via keybase chat...");
|
println!("Listening for messages via keybase chat...");
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -32,11 +32,9 @@ use error::{Error, ErrorKind};
|
||||||
use {controller, display, HTTPNodeClient, WalletConfig, WalletInst, WalletSeed};
|
use {controller, display, HTTPNodeClient, WalletConfig, WalletInst, WalletSeed};
|
||||||
use {
|
use {
|
||||||
FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter, LMDBBackend,
|
FileWalletCommAdapter, HTTPWalletCommAdapter, KeybaseWalletCommAdapter, LMDBBackend,
|
||||||
NullWalletCommAdapter,
|
NodeClient, NullWalletCommAdapter,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type WalletRef = Arc<Mutex<WalletInst<HTTPNodeClient, keychain::ExtKeychain>>>;
|
|
||||||
|
|
||||||
/// Arguments common to all wallet commands
|
/// Arguments common to all wallet commands
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GlobalArgs {
|
pub struct GlobalArgs {
|
||||||
|
@ -131,7 +129,10 @@ pub fn listen(config: &WalletConfig, args: &ListenArgs, g_args: &GlobalArgs) ->
|
||||||
Ok(())
|
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(
|
let res = controller::owner_listener(
|
||||||
wallet,
|
wallet,
|
||||||
"127.0.0.1:13420",
|
"127.0.0.1:13420",
|
||||||
|
@ -149,7 +150,10 @@ pub struct AccountArgs {
|
||||||
pub create: Option<String>,
|
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() {
|
if args.create.is_none() {
|
||||||
let res = controller::owner_single_use(wallet, |api| {
|
let res = controller::owner_single_use(wallet, |api| {
|
||||||
let acct_mappings = api.accounts()?;
|
let acct_mappings = api.accounts()?;
|
||||||
|
@ -192,7 +196,10 @@ pub struct SendArgs {
|
||||||
pub max_outputs: usize,
|
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| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
let result = api.initiate_tx(
|
let result = api.initiate_tx(
|
||||||
None,
|
None,
|
||||||
|
@ -267,7 +274,11 @@ pub struct ReceiveArgs {
|
||||||
pub message: Option<String>,
|
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 adapter = FileWalletCommAdapter::new();
|
||||||
let mut slate = adapter.receive_tx_async(&args.input)?;
|
let mut slate = adapter.receive_tx_async(&args.input)?;
|
||||||
controller::foreign_single_use(wallet, |api| {
|
controller::foreign_single_use(wallet, |api| {
|
||||||
|
@ -289,7 +300,10 @@ pub struct FinalizeArgs {
|
||||||
pub fluff: bool,
|
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 adapter = FileWalletCommAdapter::new();
|
||||||
let mut slate = adapter.receive_tx_async(&args.input)?;
|
let mut slate = adapter.receive_tx_async(&args.input)?;
|
||||||
controller::owner_single_use(wallet.clone(), |api| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
|
@ -320,7 +334,7 @@ pub struct InfoArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn info(
|
pub fn info(
|
||||||
wallet: WalletRef,
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
||||||
g_args: &GlobalArgs,
|
g_args: &GlobalArgs,
|
||||||
args: InfoArgs,
|
args: InfoArgs,
|
||||||
dark_scheme: bool,
|
dark_scheme: bool,
|
||||||
|
@ -334,7 +348,11 @@ pub fn info(
|
||||||
Ok(())
|
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| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
let (height, _) = api.node_height()?;
|
let (height, _) = api.node_height()?;
|
||||||
let (validated, outputs) = api.retrieve_outputs(g_args.show_spent, true, None)?;
|
let (validated, outputs) = api.retrieve_outputs(g_args.show_spent, true, None)?;
|
||||||
|
@ -350,7 +368,7 @@ pub struct TxsArgs {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn txs(
|
pub fn txs(
|
||||||
wallet: WalletRef,
|
wallet: Arc<Mutex<WalletInst<impl NodeClient + 'static, keychain::ExtKeychain>>>,
|
||||||
g_args: &GlobalArgs,
|
g_args: &GlobalArgs,
|
||||||
args: TxsArgs,
|
args: TxsArgs,
|
||||||
dark_scheme: bool,
|
dark_scheme: bool,
|
||||||
|
@ -385,7 +403,10 @@ pub struct RepostArgs {
|
||||||
pub fluff: bool,
|
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| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
let (_, txs) = api.retrieve_txs(true, Some(args.id), None)?;
|
let (_, txs) = api.retrieve_txs(true, Some(args.id), None)?;
|
||||||
let stored_tx = txs[0].get_stored_tx();
|
let stored_tx = txs[0].get_stored_tx();
|
||||||
|
@ -428,7 +449,10 @@ pub struct CancelArgs {
|
||||||
pub tx_id_string: String,
|
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| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
let result = api.cancel_tx(args.tx_id, args.tx_slate_id);
|
let result = api.cancel_tx(args.tx_id, args.tx_slate_id);
|
||||||
match result {
|
match result {
|
||||||
|
@ -445,7 +469,9 @@ pub fn cancel(wallet: WalletRef, args: CancelArgs) -> Result<(), Error> {
|
||||||
Ok(())
|
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| {
|
controller::owner_single_use(wallet.clone(), |api| {
|
||||||
let result = api.restore();
|
let result = api.restore();
|
||||||
match result {
|
match result {
|
||||||
|
|
|
@ -14,19 +14,36 @@
|
||||||
|
|
||||||
/// Argument parsing and error handling for wallet commands
|
/// Argument parsing and error handling for wallet commands
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
use util::Mutex;
|
||||||
|
|
||||||
use failure::Fail;
|
use failure::Fail;
|
||||||
|
|
||||||
use api::TLSConfig;
|
use api::TLSConfig;
|
||||||
use core::core;
|
use core;
|
||||||
|
use keychain;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use util::file::get_first_line;
|
use util::file::get_first_line;
|
||||||
use ErrorKind;
|
use {command, instantiate_wallet, NodeClient, WalletConfig, WalletInst, WalletSeed};
|
||||||
use {command, instantiate_wallet, WalletConfig, 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
|
/// Simple error definition, just so we can return errors from all commands
|
||||||
/// and let the caller figure out what to do
|
/// and let the caller figure out what to do
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
|
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
|
||||||
pub enum Error {
|
pub enum ParseError {
|
||||||
#[fail(display = "Invalid Arguments: {}", _0)]
|
#[fail(display = "Invalid Arguments: {}", _0)]
|
||||||
ArgumentError(String),
|
ArgumentError(String),
|
||||||
}
|
}
|
||||||
|
@ -63,14 +80,10 @@ fn prompt_password_confirm() -> String {
|
||||||
pub fn inst_wallet(
|
pub fn inst_wallet(
|
||||||
config: WalletConfig,
|
config: WalletConfig,
|
||||||
g_args: &command::GlobalArgs,
|
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 passphrase = prompt_password(&g_args.password);
|
||||||
let res = instantiate_wallet(
|
let res = instantiate_wallet(config.clone(), node_client, &passphrase, &g_args.account);
|
||||||
config.clone(),
|
|
||||||
&passphrase,
|
|
||||||
&g_args.account,
|
|
||||||
g_args.node_api_secret.clone(),
|
|
||||||
);
|
|
||||||
match res {
|
match res {
|
||||||
Ok(p) => Ok(p),
|
Ok(p) => Ok(p),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -82,31 +95,31 @@ pub fn inst_wallet(
|
||||||
_ => format!("Error instantiating wallet: {}", e),
|
_ => format!("Error instantiating wallet: {}", e),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Err(Error::ArgumentError(msg))
|
Err(ParseError::ArgumentError(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parses a required value, or throws error with message otherwise
|
// 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);
|
let arg = args.value_of(name);
|
||||||
match arg {
|
match arg {
|
||||||
Some(ar) => Ok(ar),
|
Some(ar) => Ok(ar),
|
||||||
None => {
|
None => {
|
||||||
let msg = format!("Value for argument '{}' is required in this context", name,);
|
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
|
// 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>();
|
let val = arg.parse::<u64>();
|
||||||
match val {
|
match val {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = format!("Could not parse {} as a whole number. e={}", name, 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(
|
pub fn parse_global_args(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
args: &ArgMatches,
|
args: &ArgMatches,
|
||||||
) -> Result<command::GlobalArgs, Error> {
|
) -> Result<command::GlobalArgs, ParseError> {
|
||||||
let account = parse_required(args, "account")?;
|
let account = parse_required(args, "account")?;
|
||||||
let mut show_spent = false;
|
let mut show_spent = false;
|
||||||
if args.is_present("show_spent") {
|
if args.is_present("show_spent") {
|
||||||
|
@ -133,7 +146,7 @@ pub fn parse_global_args(
|
||||||
Some(k) => k,
|
Some(k) => k,
|
||||||
None => {
|
None => {
|
||||||
let msg = format!("Private key for certificate is not set");
|
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))
|
Some(TLSConfig::new(file, key))
|
||||||
|
@ -151,18 +164,22 @@ pub fn parse_global_args(
|
||||||
|
|
||||||
pub fn parse_init_args(
|
pub fn parse_init_args(
|
||||||
config: &WalletConfig,
|
config: &WalletConfig,
|
||||||
|
g_args: &command::GlobalArgs,
|
||||||
args: &ArgMatches,
|
args: &ArgMatches,
|
||||||
) -> Result<command::InitArgs, Error> {
|
) -> Result<command::InitArgs, ParseError> {
|
||||||
if let Err(e) = WalletSeed::seed_file_exists(config) {
|
if let Err(e) = WalletSeed::seed_file_exists(config) {
|
||||||
let msg = format!("Not creating wallet - {}", e.inner);
|
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") {
|
let list_length = match args.is_present("short_wordlist") {
|
||||||
false => 32,
|
false => 32,
|
||||||
true => 16,
|
true => 16,
|
||||||
};
|
};
|
||||||
println!("Please enter a password for your new wallet");
|
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 {
|
Ok(command::InitArgs {
|
||||||
list_length: list_length,
|
list_length: list_length,
|
||||||
password: password,
|
password: password,
|
||||||
|
@ -173,14 +190,14 @@ pub fn parse_init_args(
|
||||||
pub fn parse_recover_args(
|
pub fn parse_recover_args(
|
||||||
g_args: &command::GlobalArgs,
|
g_args: &command::GlobalArgs,
|
||||||
args: &ArgMatches,
|
args: &ArgMatches,
|
||||||
) -> Result<command::RecoverArgs, Error> {
|
) -> Result<command::RecoverArgs, ParseError> {
|
||||||
let (passphrase, recovery_phrase) = {
|
let (passphrase, recovery_phrase) = {
|
||||||
match args.value_of("recovery_phrase") {
|
match args.value_of("recovery_phrase") {
|
||||||
None => (prompt_password(&g_args.password), None),
|
None => (prompt_password(&g_args.password), None),
|
||||||
Some(l) => {
|
Some(l) => {
|
||||||
if WalletSeed::from_mnemonic(l).is_err() {
|
if WalletSeed::from_mnemonic(l).is_err() {
|
||||||
let msg = format!("Recovery word phrase is invalid");
|
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");
|
println!("Please provide a new password for the recovered wallet");
|
||||||
(prompt_password_confirm(), Some(l.to_owned()))
|
(prompt_password_confirm(), Some(l.to_owned()))
|
||||||
|
@ -197,7 +214,7 @@ pub fn parse_listen_args(
|
||||||
config: &mut WalletConfig,
|
config: &mut WalletConfig,
|
||||||
g_args: &mut command::GlobalArgs,
|
g_args: &mut command::GlobalArgs,
|
||||||
args: &ArgMatches,
|
args: &ArgMatches,
|
||||||
) -> Result<command::ListenArgs, Error> {
|
) -> Result<command::ListenArgs, ParseError> {
|
||||||
// listen args
|
// listen args
|
||||||
let pass = match g_args.password.clone() {
|
let pass = match g_args.password.clone() {
|
||||||
Some(p) => Some(p.to_owned()),
|
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") {
|
let create = match account_args.value_of("create") {
|
||||||
None => None,
|
None => None,
|
||||||
Some(s) => Some(s.to_owned()),
|
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 })
|
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
|
// amount
|
||||||
let amount = parse_required(args, "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 {
|
let amount = match amount {
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(e) => {
|
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={}",
|
"Could not parse amount as a number with optional decimal point. e={}",
|
||||||
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://: {}",
|
"HTTP Destination should start with http://: or https://: {}",
|
||||||
dest,
|
dest,
|
||||||
);
|
);
|
||||||
return Err(Error::ArgumentError(msg));
|
return Err(ParseError::ArgumentError(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// change_outputs
|
// 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
|
// message
|
||||||
let message = match receive_args.is_present("message") {
|
let message = match receive_args.is_present("message") {
|
||||||
true => Some(receive_args.value_of("message").unwrap().to_owned()),
|
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
|
// validate input
|
||||||
if !Path::new(&tx_file).is_file() {
|
if !Path::new(&tx_file).is_file() {
|
||||||
let msg = format!("File {} not found.", &tx_file);
|
let msg = format!("File {} not found.", &tx_file);
|
||||||
return Err(Error::ArgumentError(msg));
|
return Err(ParseError::ArgumentError(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(command::ReceiveArgs {
|
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 fluff = args.is_present("fluff");
|
||||||
let tx_file = parse_required(args, "input")?;
|
let tx_file = parse_required(args, "input")?;
|
||||||
|
|
||||||
if !Path::new(&tx_file).is_file() {
|
if !Path::new(&tx_file).is_file() {
|
||||||
let msg = format!("File {} not found.", tx_file);
|
let msg = format!("File {} not found.", tx_file);
|
||||||
return Err(Error::ArgumentError(msg));
|
return Err(ParseError::ArgumentError(msg));
|
||||||
}
|
}
|
||||||
Ok(command::FinalizeArgs {
|
Ok(command::FinalizeArgs {
|
||||||
input: tx_file.to_owned(),
|
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
|
// minimum_confirmations
|
||||||
let mc = parse_required(args, "minimum_confirmations")?;
|
let mc = parse_required(args, "minimum_confirmations")?;
|
||||||
let mc = parse_u64(mc, "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") {
|
let tx_id = match args.value_of("id") {
|
||||||
None => None,
|
None => None,
|
||||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
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 })
|
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") {
|
let tx_id = match args.value_of("id") {
|
||||||
None => None,
|
None => None,
|
||||||
Some(tx) => Some(parse_u64(tx, "id")? as u32),
|
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 mut tx_id_string = "";
|
||||||
let tx_id = match args.value_of("id") {
|
let tx_id = match args.value_of("id") {
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -381,13 +398,13 @@ pub fn parse_cancel_args(args: &ArgMatches) -> Result<command::CancelArgs, Error
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let msg = format!("Could not parse txid parameter. e={}", 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()) {
|
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.");
|
let msg = format!("'id' (-i) or 'txid' (-t) argument is required.");
|
||||||
return Err(Error::ArgumentError(msg));
|
return Err(ParseError::ArgumentError(msg));
|
||||||
}
|
}
|
||||||
Ok(command::CancelArgs {
|
Ok(command::CancelArgs {
|
||||||
tx_id: tx_id,
|
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(),
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -96,6 +96,10 @@ pub enum ErrorKind {
|
||||||
#[fail(display = "BIP39 Mnemonic (word list) Error")]
|
#[fail(display = "BIP39 Mnemonic (word list) Error")]
|
||||||
Mnemonic,
|
Mnemonic,
|
||||||
|
|
||||||
|
/// Command line argument error
|
||||||
|
#[fail(display = "{}", _0)]
|
||||||
|
ArgumentError(String),
|
||||||
|
|
||||||
/// Other
|
/// Other
|
||||||
#[fail(display = "Generic error: {}", _0)]
|
#[fail(display = "Generic error: {}", _0)]
|
||||||
GenericError(String),
|
GenericError(String),
|
||||||
|
|
|
@ -76,14 +76,13 @@ use util::Mutex;
|
||||||
/// Helper to create an instance of the LMDB wallet
|
/// Helper to create an instance of the LMDB wallet
|
||||||
pub fn instantiate_wallet(
|
pub fn instantiate_wallet(
|
||||||
wallet_config: WalletConfig,
|
wallet_config: WalletConfig,
|
||||||
|
node_client: impl NodeClient + 'static,
|
||||||
passphrase: &str,
|
passphrase: &str,
|
||||||
account: &str,
|
account: &str,
|
||||||
node_api_secret: Option<String>,
|
) -> Result<Arc<Mutex<WalletInst<impl NodeClient, keychain::ExtKeychain>>>, Error> {
|
||||||
) -> Result<Arc<Mutex<WalletInst<HTTPNodeClient, keychain::ExtKeychain>>>, Error> {
|
|
||||||
// First test decryption, so we can abort early if we have the wrong password
|
// First test decryption, so we can abort early if we have the wrong password
|
||||||
let _ = WalletSeed::from_file(&wallet_config, passphrase)?;
|
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, node_client)?;
|
||||||
let mut db_wallet = LMDBBackend::new(wallet_config.clone(), passphrase, client_n)?;
|
|
||||||
db_wallet.set_parent_key_id_by_name(account)?;
|
db_wallet.set_parent_key_id_by_name(account)?;
|
||||||
info!("Using LMDB Backend for wallet");
|
info!("Using LMDB Backend for wallet");
|
||||||
Ok(Arc::new(Mutex::new(db_wallet)))
|
Ok(Arc::new(Mutex::new(db_wallet)))
|
||||||
|
|
|
@ -188,9 +188,15 @@ pub trait NodeClient: Sync + Send + Clone {
|
||||||
/// Return the URL of the check node
|
/// Return the URL of the check node
|
||||||
fn node_url(&self) -> &str;
|
fn node_url(&self) -> &str;
|
||||||
|
|
||||||
|
/// Set the node URL
|
||||||
|
fn set_node_url(&mut self, node_url: &str);
|
||||||
|
|
||||||
/// Return the node api secret
|
/// Return the node api secret
|
||||||
fn node_api_secret(&self) -> Option<String>;
|
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
|
/// Posts a transaction to a grin node
|
||||||
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), Error>;
|
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), Error>;
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,14 @@ impl NodeClient for HTTPNodeClient {
|
||||||
self.node_api_secret.clone()
|
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
|
/// Posts a transaction to a grin node
|
||||||
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> {
|
fn post_tx(&self, tx: &TxWrapper, fluff: bool) -> Result<(), libwallet::Error> {
|
||||||
let url;
|
let url;
|
||||||
|
|
245
wallet/tests/command_line.rs
Normal file
245
wallet/tests/command_line.rs
Normal 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(¤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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -393,7 +393,8 @@ impl NodeClient for LocalWalletClient {
|
||||||
fn node_api_secret(&self) -> Option<String> {
|
fn node_api_secret(&self) -> Option<String> {
|
||||||
None
|
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
|
/// Posts a transaction to a grin node
|
||||||
/// In this case it will create a new block with award rewarded to
|
/// In this case it will create a new block with award rewarded to
|
||||||
fn post_tx(&self, tx: &TxWrapper, _fluff: bool) -> Result<(), libwallet::Error> {
|
fn post_tx(&self, tx: &TxWrapper, _fluff: bool) -> Result<(), libwallet::Error> {
|
||||||
|
|
Loading…
Reference in a new issue