From b83119233552860758b17dbb8802494bc9289c76 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Fri, 10 Nov 2017 14:03:31 +0000 Subject: [PATCH] added wallet info/outputs commands with pretty printing (#254) * added wallet info/outputs commands with pretty printing * added confirmed but locked to display output --- src/bin/grin.rs | 8 +++- wallet/Cargo.toml | 2 + wallet/src/info.rs | 78 ++++++++++++++++++++++-------------- wallet/src/lib.rs | 5 +++ wallet/src/outputs.rs | 92 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 30 deletions(-) create mode 100644 wallet/src/outputs.rs diff --git a/src/bin/grin.rs b/src/bin/grin.rs index e2ea8ce75..1de36cc74 100644 --- a/src/bin/grin.rs +++ b/src/bin/grin.rs @@ -229,8 +229,11 @@ fn main() { .default_value("1") .takes_value(true))) + .subcommand(SubCommand::with_name("outputs") + .about("raw wallet info (list of outputs)")) + .subcommand(SubCommand::with_name("info") - .about("basic wallet info (outputs)")) + .about("basic wallet contents summary")) .subcommand(SubCommand::with_name("init") .about("Initialize a new wallet seed file."))) @@ -431,6 +434,9 @@ fn wallet_command(wallet_args: &ArgMatches) { ("info", Some(_)) => { wallet::show_info(&wallet_config, &keychain); } + ("outputs", Some(_)) => { + wallet::show_outputs(&wallet_config, &keychain); + } _ => panic!("Unknown wallet command, use 'grin help wallet' for details"), } } diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index f3a8ea02c..7d7638201 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -23,6 +23,8 @@ hyper = "~0.11.4" tokio-core="~0.1.1" tokio-retry="~0.1.0" router = "~0.5.1" +prettytable-rs = "^0.6" +term = "~0.4.6" grin_api = { path = "../api" } grin_core = { path = "../core" } grin_keychain = { path = "../keychain" } diff --git a/wallet/src/info.rs b/wallet/src/info.rs index 89cadf070..64b8a7d1b 100644 --- a/wallet/src/info.rs +++ b/wallet/src/info.rs @@ -14,17 +14,17 @@ use checker; use keychain::Keychain; -use core::core; -use types::{WalletConfig, WalletData}; +use core::core::amount_to_hr_string; +use types::{WalletConfig, WalletData, OutputStatus}; +use prettytable; +use term; +use std::io::prelude::*; pub fn show_info(config: &WalletConfig, keychain: &Keychain) { - let root_key_id = keychain.root_key_id(); let result = checker::refresh_outputs(&config, &keychain); - // just read the wallet here, no need for a write lock + let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| { - // get the current height via the api - // if we cannot get the current height use the max height known to the wallet let current_height = match checker::get_tip_from_node(config) { Ok(tip) => tip.height, Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() { @@ -32,32 +32,52 @@ pub fn show_info(config: &WalletConfig, keychain: &Keychain) { None => 0, }, }; - - println!("Outputs - "); - println!("key_id, height, lock_height, status, coinbase?, num_confs, value"); - println!("----------------------------------"); - - let mut outputs = wallet_data + let mut unspent_total=0; + let mut unspent_but_locked_total=0; + let mut unconfirmed_total=0; + let mut locked_total=0; + for out in wallet_data .outputs .values() - .filter(|out| out.root_key_id == root_key_id) - .collect::>(); - outputs.sort_by_key(|out| out.n_child); - for out in outputs { - println!( - "{}, {}, {}, {:?}, {}, {}, {}", - out.key_id, - out.height, - out.lock_height, - out.status, - out.is_coinbase, - out.num_confirmations(current_height), - core::amount_to_hr_string(out.value), - ); - } + .filter(|out| out.root_key_id == keychain.root_key_id()) + { + if out.status == OutputStatus::Unspent { + unspent_total+=out.value; + if out.lock_height > current_height { + unspent_but_locked_total+=out.value; + } + } + if out.status == OutputStatus::Unconfirmed && !out.is_coinbase { + unconfirmed_total+=out.value; + } + if out.status == OutputStatus::Locked { + locked_total+=out.value; + } + }; + + + println!(); + let title=format!("Wallet Summary Info - Block Height: {}", current_height); + let mut t = term::stdout().unwrap(); + t.fg(term::color::MAGENTA).unwrap(); + writeln!(t, "{}", title).unwrap(); + writeln!(t, "--------------------------").unwrap(); + t.reset().unwrap(); + + let mut table = table!( + [bFG->"Total", FG->amount_to_hr_string(unspent_total+unconfirmed_total)], + [bFY->"Awaiting Confirmation", FY->amount_to_hr_string(unconfirmed_total)], + [bFY->"Confirmed but Still Locked", FY->amount_to_hr_string(unspent_but_locked_total)], + [bFG->"Currently Spendable", FG->amount_to_hr_string(unspent_total-unspent_but_locked_total)], + [Fw->"---------", Fw->"---------"], + [Fr->"(Locked by previous transaction)", Fr->amount_to_hr_string(locked_total)] + ); + table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.printstd(); + println!(); }); - if let Err(e) = result { - println!("WARNING - Showing local data only - Wallet was unable to contact a node to update and verify the outputs shown here."); + if let Err(_) = result { + println!("WARNING - Showing local data only - Wallet was unable to contact a node to update and verify the info shown here."); } } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 8323c138c..f5bf27a8e 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -23,6 +23,9 @@ extern crate serde_derive; extern crate serde_json; #[macro_use] extern crate slog; +#[macro_use] +extern crate prettytable; +extern crate term; extern crate bodyparser; extern crate futures; @@ -40,6 +43,7 @@ extern crate grin_util as util; mod checker; mod handlers; +mod outputs; mod info; mod receiver; mod sender; @@ -47,6 +51,7 @@ mod types; pub mod client; pub mod server; +pub use outputs::show_outputs; pub use info::show_info; pub use receiver::{receive_json_tx, receive_json_tx_str, WalletReceiver}; pub use sender::{issue_burn_tx, issue_send_tx}; diff --git a/wallet/src/outputs.rs b/wallet/src/outputs.rs new file mode 100644 index 000000000..b72193534 --- /dev/null +++ b/wallet/src/outputs.rs @@ -0,0 +1,92 @@ +// Copyright 2017 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 checker; +use keychain::Keychain; +use core::core; +use types::{WalletConfig, WalletData}; +use prettytable; +use term; +use std::io::prelude::*; + +pub fn show_outputs(config: &WalletConfig, keychain: &Keychain) { + let root_key_id = keychain.root_key_id(); + let result = checker::refresh_outputs(&config, &keychain); + + // just read the wallet here, no need for a write lock + let _ = WalletData::read_wallet(&config.data_file_dir, |wallet_data| { + // get the current height via the api + // if we cannot get the current height use the max height known to the wallet + let current_height = match checker::get_tip_from_node(config) { + Ok(tip) => tip.height, + Err(_) => match wallet_data.outputs.values().map(|out| out.height).max() { + Some(height) => height, + None => 0, + }, + }; + + let mut outputs = wallet_data + .outputs + .values() + .filter(|out| out.root_key_id == root_key_id) + .collect::>(); + outputs.sort_by_key(|out| out.n_child); + + let title=format!("Wallet Outputs - Block Height: {}", current_height); + println!(); + let mut t = term::stdout().unwrap(); + t.fg(term::color::MAGENTA).unwrap(); + writeln!(t, "{}", title).unwrap(); + t.reset().unwrap(); + + let mut table = table!(); + + table.set_titles(row![ + bMG->"Key Id", + bMG->"Block Height", + bMG->"Locked Until", + bMG->"Status", + bMG->"Is Coinbase?", + bMG->"Num. of Confirmations", + bMG->"Value" + ]); + + for out in outputs { + let key_id=format!("{}", out.key_id); + let height=format!("{}", out.height); + let lock_height=format!("{}", out.lock_height); + let status=format!("{:?}", out.status); + let is_coinbase=format!("{}", out.is_coinbase); + let num_confirmations=format!("{}", out.num_confirmations(current_height)); + let value=format!("{}", core::amount_to_hr_string(out.value)); + table.add_row(row![ + bFC->key_id, + bFB->height, + bFB->lock_height, + bFR->status, + bFY->is_coinbase, + bFB->num_confirmations, + bFG->value + ]); + } + + table.set_format(*prettytable::format::consts::FORMAT_NO_COLSEP); + table.printstd(); + println!(); + }); + + if let Err(_) = result { + println!("WARNING - Showing local data only - Wallet was unable to contact a node to update and verify the outputs shown here."); + } +}