diff --git a/applications/tari_base_node/src/command_handler.rs b/applications/tari_base_node/src/command_handler.rs index 5ff4d38725..d1b24b099f 100644 --- a/applications/tari_base_node/src/command_handler.rs +++ b/applications/tari_base_node/src/command_handler.rs @@ -104,6 +104,8 @@ impl CommandHandler { self.executor.spawn(async move { let mut status_line = StatusLine::new(); + let version = format!("v{}", consts::APP_VERSION_NUMBER); + status_line.add_field("", version); let state = state_info.recv().await.unwrap(); status_line.add_field("State", state.state_info.short_desc()); diff --git a/applications/tari_base_node/src/status_line.rs b/applications/tari_base_node/src/status_line.rs index dfd7d13306..9914c660cd 100644 --- a/applications/tari_base_node/src/status_line.rs +++ b/applications/tari_base_node/src/status_line.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use chrono::Utc; +use chrono::Local; use std::{fmt, fmt::Display}; #[derive(Debug, Clone, Default)] @@ -41,13 +41,37 @@ impl StatusLine { impl Display for StatusLine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}: ", Utc::now().format("%H:%M"))?; - let s = self - .fields - .iter() - .map(|(k, v)| format!("{}: {}", k, v)) - .collect::>(); + write!(f, "{} ", Local::now().format("%H:%M"))?; + let s = self.fields.iter().map(|(k, v)| format(k, v)).collect::>(); write!(f, "{}", s.join(", ")) } } + +fn format(k: &&str, v: &str) -> String { + if k.is_empty() { + v.to_string() + } else { + format!("{}: {}", k, v) + } +} + +#[cfg(test)] +mod test { + use super::StatusLine; + + #[test] + fn test_do_not_display_empty_keys() { + let mut status = StatusLine::new(); + status.add_field("key", "val"); + let display = status.to_string(); + assert!(display.contains("key: val")); + assert_eq!(display.matches(':').count(), 2); + + let mut status = StatusLine::new(); + status.add_field("", "val"); + let display = status.to_string(); + assert!(display.contains("val")); + assert_eq!(display.matches(':').count(), 1); + } +} diff --git a/applications/tari_console_wallet/README.md b/applications/tari_console_wallet/README.md index 0cdec4d632..7fce2d0931 100644 --- a/applications/tari_console_wallet/README.md +++ b/applications/tari_console_wallet/README.md @@ -2,15 +2,18 @@ The Tari Console Wallet is a terminal based wallet for sending and receiving Tari. It can be run in a few different modes. -### Terminal UI (TUI) mode +## Terminal UI (TUI) mode + The standard operating mode, TUI mode is the default when starting `tari_console_wallet`. Displays a UI in the terminal to interact with the wallet. ![](./docs/img/tui.png) -### Daemon (GRPC) mode +## Daemon (GRPC) mode + Run as a server with no UI, but exposing the GRPC interface with `tari_console_wallet --daemon`. -### Command mode +## Command mode + Run a once off command with the `--command` argument: - **get-balance** @@ -20,6 +23,7 @@ Get your wallet balance `tari_console_wallet --command "get-balance"` example output: + ``` Available balance: 1268922.299856 T Pending incoming balance: 6010 µT @@ -33,6 +37,7 @@ Send an amount of Tari to a public key or emoji id. `tari_console_wallet --command "send-tari "` example: + ``` $ tari_console_wallet --command "send-tari 1T c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 coffee" @@ -49,8 +54,9 @@ Make it rain! Send many transactions to a public key or emoji id. `tari_console_wallet --command "make-it-rain "` example: + ``` -$ tari_console_wallet --command "make-it-rain 1 10 8000 100 now c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 makin it rain yo" +$ tari_console_wallet --command "make-it-rain 1 10 8000 100 now c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 makin it rain yo" 1. make-it-rain 1 10 8000 µT 100 µT 2021-03-26 10:03:30.459157 UTC c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 makin it rain yo @@ -64,11 +70,11 @@ Split one or more unspent transaction outputs into many. Creates a transaction that must be mined before the new outputs can be spent. `tari_console_wallet --command "coin-split "` + example: -$ tari_console_wallet --command "coin-split 10000 9" -example output: + ``` -$ tari_console_wallet --command "coin-split 10000 µT 9" +$ tari_console_wallet --command "coin-split 10000 9" 1. coin-split 10000 µT 9 @@ -77,10 +83,61 @@ Monitoring 1 sent transactions to Broadcast stage... Done! All transactions monitored to Broadcast stage. ``` +- **set-base-node** + +Sets the base node peer that the wallet should connect to (not persisted after exit, normally used in a script). + +`tari_console_wallet --command "set-base-node "` + +example: + +``` +$ tari_console_wallet --command "set-base-node 3883ab92d91eb70155d1d471c9e569d2bcae10ee3f196b8dfdaade1e7546c520 /onion3/wlyt2p4ft4mtj6zs2fdgw6hwfqvf5i4hhia4y6ffk6oybfsbrwqcpead:18141" + +1. set-base-node 3883ab92d91eb70155d1d471c9e569d2bcae10ee3f196b8dfdaade1e7546c520 /onion3/wlyt2p4ft4mtj6zs2fdgw6hwfqvf5i4hhia4y6ffk6oybfsbrwqcpead:18141 + +Setting base node peer... +3883ab92d91eb70155d1d471c9e569d2bcae10ee3f196b8dfdaade1e7546c520::/onion3/wlyt2p4ft4mtj6zs2fdgw6hwfqvf5i4hhia4y6ffk6oybfsbrwqcpead:18141 +``` + +- **set-custom-base-node** + +Sets the custom base node peer that the wallet should connect to, and persists the peer to the wallet database. + +`tari_console_wallet --command "set-custom-base-node "` + +example: + +``` +$ tari_console_wallet --command "set-custom-base-node 3883ab92d91eb70155d1d471c9e569d2bcae10ee3f196b8dfdaade1e7546c520 /onion3/wlyt2p4ft4mtj6zs2fdgw6hwfqvf5i4hhia4y6ffk6oybfsbrwqcpead:18141" + +1. set-custom-base-node 3883ab92d91eb70155d1d471c9e569d2bcae10ee3f196b8dfdaade1e7546c520 /onion3/wlyt2p4ft4mtj6zs2fdgw6hwfqvf5i4hhia4y6ffk6oybfsbrwqcpead:18141 + +Setting base node peer... +3883ab92d91eb70155d1d471c9e569d2bcae10ee3f196b8dfdaade1e7546c520::/onion3/wlyt2p4ft4mtj6zs2fdgw6hwfqvf5i4hhia4y6ffk6oybfsbrwqcpead:18141 +Saving custom base node peer in wallet database. +``` + +- **clear-custom-base-node** + +Clears the custom base node peer from the wallet database. + +`tari_console_wallet --command "clear-custom-base-node"` + +example: + +``` +$ tari_console_wallet --command clear-custom-base-node + +1. clear-custom-base-node + +Clearing custom base node peer in wallet database. +``` + - **export-utxos** -Export all the unspent transaction outputs (UTXOs) in the wallet. This can either list the UTXOs directly in the -console, or write them to file. In the latter case the complete unblinded set of information will be exported. +Export all the unspent transaction outputs (UTXOs) in the wallet. This can either list the UTXOs directly in the +console, or write them to file. In the latter case the complete unblinded set of information will be exported. ``` tari_console_wallet --command "export-utxos" @@ -88,8 +145,9 @@ tari_console_wallet --command "export-utxos --csv-file " ``` example output - console only: + ``` -$ tari_console_wallet --command "export-utxos" +$ tari_console_wallet --command "export-utxos" 1. export-utxos @@ -103,8 +161,9 @@ Total value of UTXOs: 1268921.295856 T ``` example output - `--csv-file` (console output): + ``` -$ tari_console_wallet --command "export-utxos --csv-file utxos.csv" +$ tari_console_wallet --command "export-utxos --csv-file utxos.csv" 1. export-utxos --csv-file utxos.csv @@ -113,6 +172,7 @@ Total value of UTXOs: 36105.165440 T ``` example output - `--csv-file` (contents of `utxos.csv`) + ``` "#","Value (uT)","Spending Key","Commitment","Flags","Maturity" "1","121999250","0b0ce2add569845ec8bb84256b731e644e2224580b568e75666399e868ea5701","22514e279bd7e7e0a6e45905e07323b16f6114e300bcc02f36b2baf44a17b43d","(empty)","0" @@ -130,6 +190,7 @@ Count the number of unspent transaction outputs (UTXOs) in the wallet. `tari_console_wallet --command "count-utxos"` example output: + ``` 1. count-utxos @@ -147,6 +208,7 @@ Discover a peer on the network by public key or emoji id. `tari_console_wallet --command "discover-peer "` example output: + ``` 1. discover-peer c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 @@ -163,6 +225,7 @@ Look up a public key or emoji id, useful for converting between the two formats. `tari_console_wallet --command "whois "` example output: + ``` 1. whois c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 @@ -170,11 +233,12 @@ Public Key: c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 Emoji ID : 📈👛💭🎾🌍👡🌋😻🚀🏉🔥🚓🍳👹👿🍕🐵🐼💡💦🎺👘🚌🚿👻🐛🏉🍵🏥🚌🍑🌞🍹 ``` -### Script mode +## Script mode -Run a series of commands from a given script. +Run a series of commands from a given script. The commands should be formatted the same way as Command mode, one per line in a text file. `tari_console_wallet --script /path/to/script` -### Recovery mode +## Recovery mode + todo docs diff --git a/applications/tari_console_wallet/src/automation/command_parser.rs b/applications/tari_console_wallet/src/automation/command_parser.rs index 9c55d4cedc..6e03d638f8 100644 --- a/applications/tari_console_wallet/src/automation/command_parser.rs +++ b/applications/tari_console_wallet/src/automation/command_parser.rs @@ -30,6 +30,7 @@ use std::{ str::FromStr, }; use tari_app_utilities::utilities::parse_emoji_id_or_public_key; +use tari_comms::multiaddr::Multiaddr; use tari_core::transactions::{tari_amount::MicroTari, types::PublicKey}; @@ -41,17 +42,21 @@ pub struct ParsedCommand { impl Display for ParsedCommand { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use WalletCommand::*; let command = match self.command { - WalletCommand::GetBalance => "get-balance", - WalletCommand::SendTari => "send-tari", - WalletCommand::SendOneSided => "send-one-sided", - WalletCommand::MakeItRain => "make-it-rain", - WalletCommand::CoinSplit => "coin-split", - WalletCommand::DiscoverPeer => "discover-peer", - WalletCommand::Whois => "whois", - WalletCommand::ExportUtxos => "export-utxos", - WalletCommand::ExportSpentUtxos => "export-spent-utxos", - WalletCommand::CountUtxos => "count-utxos", + GetBalance => "get-balance", + SendTari => "send-tari", + SendOneSided => "send-one-sided", + MakeItRain => "make-it-rain", + CoinSplit => "coin-split", + DiscoverPeer => "discover-peer", + Whois => "whois", + ExportUtxos => "export-utxos", + ExportSpentUtxos => "export-spent-utxos", + CountUtxos => "count-utxos", + SetBaseNode => "set-base-node", + SetCustomBaseNode => "set-custom-base-node", + ClearCustomBaseNode => "clear-custom-base-node", }; let args = self @@ -75,19 +80,22 @@ pub enum ParsedArgument { Date(DateTime), OutputToCSVFile(String), CSVFileName(String), + Address(Multiaddr), } impl Display for ParsedArgument { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use ParsedArgument::*; match self { - ParsedArgument::Amount(v) => write!(f, "{}", v.to_string()), - ParsedArgument::PublicKey(v) => write!(f, "{}", v.to_string()), - ParsedArgument::Text(v) => write!(f, "{}", v.to_string()), - ParsedArgument::Float(v) => write!(f, "{}", v.to_string()), - ParsedArgument::Int(v) => write!(f, "{}", v.to_string()), - ParsedArgument::Date(v) => write!(f, "{}", v.to_string()), - ParsedArgument::OutputToCSVFile(v) => write!(f, "{}", v.to_string()), - ParsedArgument::CSVFileName(v) => write!(f, "{}", v.to_string()), + Amount(v) => write!(f, "{}", v.to_string()), + PublicKey(v) => write!(f, "{}", v.to_string()), + Text(v) => write!(f, "{}", v.to_string()), + Float(v) => write!(f, "{}", v.to_string()), + Int(v) => write!(f, "{}", v.to_string()), + Date(v) => write!(f, "{}", v.to_string()), + OutputToCSVFile(v) => write!(f, "{}", v.to_string()), + CSVFileName(v) => write!(f, "{}", v.to_string()), + Address(v) => write!(f, "{}", v.to_string()), } } } @@ -106,11 +114,14 @@ pub fn parse_command(command: &str) -> Result { SendOneSided => parse_send_tari(args)?, MakeItRain => parse_make_it_rain(args)?, CoinSplit => parse_coin_split(args)?, - DiscoverPeer => parse_discover_peer(args)?, + DiscoverPeer => parse_public_key(args)?, Whois => parse_whois(args)?, ExportUtxos => parse_export_utxos(args)?, // todo: only show X number of utxos ExportSpentUtxos => parse_export_spent_utxos(args)?, // todo: only show X number of utxos CountUtxos => Vec::new(), + SetBaseNode => parse_public_key_and_address(args)?, + SetCustomBaseNode => parse_public_key_and_address(args)?, + ClearCustomBaseNode => Vec::new(), }; Ok(ParsedCommand { command, args }) @@ -129,7 +140,7 @@ fn parse_whois(mut args: SplitWhitespace) -> Result, ParseEr Ok(parsed_args) } -fn parse_discover_peer(mut args: SplitWhitespace) -> Result, ParseError> { +fn parse_public_key(mut args: SplitWhitespace) -> Result, ParseError> { let mut parsed_args = Vec::new(); // public key/emoji id @@ -142,6 +153,26 @@ fn parse_discover_peer(mut args: SplitWhitespace) -> Result, Ok(parsed_args) } +fn parse_public_key_and_address(mut args: SplitWhitespace) -> Result, ParseError> { + let mut parsed_args = Vec::new(); + + // public key/emoji id + let pubkey = args + .next() + .ok_or_else(|| ParseError::Empty("public key or emoji id".to_string()))?; + let pubkey = parse_emoji_id_or_public_key(pubkey).ok_or(ParseError::PublicKey)?; + parsed_args.push(ParsedArgument::PublicKey(pubkey)); + + // address + let address = args + .next() + .ok_or_else(|| ParseError::Empty("net address".to_string()))?; + let address = address.parse::().map_err(|_| ParseError::Address)?; + parsed_args.push(ParsedArgument::Address(address)); + + Ok(parsed_args) +} + fn parse_make_it_rain(mut args: SplitWhitespace) -> Result, ParseError> { let mut parsed_args = Vec::new(); diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 3f14a77cad..4fe02c9334 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -21,7 +21,10 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use super::error::CommandError; -use crate::automation::command_parser::{ParsedArgument, ParsedCommand}; +use crate::{ + automation::command_parser::{ParsedArgument, ParsedCommand}, + utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, +}; use chrono::{DateTime, Utc}; use futures::{FutureExt, StreamExt}; use log::*; @@ -33,7 +36,11 @@ use std::{ }; use strum_macros::{Display, EnumIter, EnumString}; use tari_common::GlobalConfig; -use tari_comms::connectivity::{ConnectivityEvent, ConnectivityRequester}; +use tari_comms::{ + connectivity::{ConnectivityEvent, ConnectivityRequester}, + multiaddr::Multiaddr, + types::CommsPublicKey, +}; use tari_comms_dht::{envelope::NodeDestination, DhtDiscoveryRequester}; use tari_core::{ tari_utilities::hex::Hex, @@ -71,6 +78,9 @@ pub enum WalletCommand { ExportUtxos, ExportSpentUtxos, CountUtxos, + SetBaseNode, + SetCustomBaseNode, + ClearCustomBaseNode, } #[derive(Debug, EnumString, PartialEq, Clone)] @@ -186,6 +196,28 @@ async fn wait_for_comms(connectivity_requester: &ConnectivityRequester) -> Resul } } } +async fn set_base_node_peer( + mut wallet: WalletSqlite, + args: &[ParsedArgument], +) -> Result<(CommsPublicKey, Multiaddr), CommandError> { + let public_key = match args[0].clone() { + ParsedArgument::PublicKey(s) => Ok(s), + _ => Err(CommandError::Argument), + }?; + + let net_address = match args[1].clone() { + ParsedArgument::Address(a) => Ok(a), + _ => Err(CommandError::Argument), + }?; + + println!("Setting base node peer..."); + println!("{}::{}", public_key, net_address); + wallet + .set_base_node_peer(public_key.clone(), net_address.to_string()) + .await?; + + Ok((public_key, net_address)) +} pub async fn discover_peer( mut dht_service: DhtDiscoveryRequester, @@ -540,6 +572,32 @@ pub async fn command_runner( println!("Maximum value UTXO : {}", max); } }, + SetBaseNode => { + set_base_node_peer(wallet.clone(), &parsed.args).await?; + }, + SetCustomBaseNode => { + let (public_key, net_address) = set_base_node_peer(wallet.clone(), &parsed.args).await?; + println!("Saving custom base node peer in wallet database."); + wallet + .db + .set_client_key_value(CUSTOM_BASE_NODE_PUBLIC_KEY_KEY.to_string(), public_key.to_string()) + .await?; + wallet + .db + .set_client_key_value(CUSTOM_BASE_NODE_ADDRESS_KEY.to_string(), net_address.to_string()) + .await?; + }, + ClearCustomBaseNode => { + println!("Clearing custom base node peer in wallet database."); + wallet + .db + .clear_client_value(CUSTOM_BASE_NODE_PUBLIC_KEY_KEY.to_string()) + .await?; + wallet + .db + .clear_client_value(CUSTOM_BASE_NODE_ADDRESS_KEY.to_string()) + .await?; + }, } } diff --git a/applications/tari_console_wallet/src/automation/error.rs b/applications/tari_console_wallet/src/automation/error.rs index e5d443f3df..b1f9a7b4c3 100644 --- a/applications/tari_console_wallet/src/automation/error.rs +++ b/applications/tari_console_wallet/src/automation/error.rs @@ -27,6 +27,7 @@ use log::*; use tari_app_utilities::utilities::ExitCodes; use tari_core::transactions::{tari_amount::MicroTariError, transaction::TransactionError}; use tari_wallet::{ + error::{WalletError, WalletStorageError}, output_manager_service::error::OutputManagerError, transaction_service::error::TransactionServiceError, }; @@ -54,6 +55,10 @@ pub enum CommandError { Comms(String), #[error("CSV file error `{0}`")] CSVFile(String), + #[error("Wallet error `{0}`")] + WalletError(#[from] WalletError), + #[error("Wallet storage error `{0}`")] + WalletStorageError(#[from] WalletStorageError), } impl From for ExitCodes { @@ -79,6 +84,8 @@ pub enum ParseError { Int(#[from] ParseIntError), #[error("Failed to parse date. {0}")] Date(#[from] DateError), + #[error("Failed to parse a net address.")] + Address, #[error("Invalid combination of arguments.")] Invalid, #[error("Parsing not yet implemented for {0}.")] diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index 16490e212d..60784e0b19 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -196,9 +196,9 @@ pub fn wallet_mode(bootstrap: &ConfigBootstrap, boot_mode: WalletBoot) -> Wallet // GRPC daemon mode (true, None, None) => WalletMode::Grpc, // Script mode - (false, Some(path), None) => WalletMode::Script(path), + (_, Some(path), None) => WalletMode::Script(path), // Command mode - (false, None, Some(command)) => WalletMode::Command(command), + (_, None, Some(command)) => WalletMode::Command(command), // Invalid combinations _ => WalletMode::Invalid, } diff --git a/applications/tari_console_wallet/src/main.rs b/applications/tari_console_wallet/src/main.rs index 4eedac3ac7..2257740bab 100644 --- a/applications/tari_console_wallet/src/main.rs +++ b/applications/tari_console_wallet/src/main.rs @@ -6,7 +6,7 @@ #![deny(unreachable_patterns)] #![deny(unknown_lints)] #![recursion_limit = "1024"] -use crate::recovery::get_private_key_from_seed_words; +use crate::{recovery::get_private_key_from_seed_words, wallet_modes::WalletModeConfig}; use init::{ boot, change_password, @@ -62,7 +62,7 @@ fn main_inner() -> Result<(), ExitCodes> { .build() .expect("Failed to build a runtime!"); - let (bootstrap, config, _) = init_configuration(ApplicationType::ConsoleWallet)?; + let (bootstrap, global_config, _) = init_configuration(ApplicationType::ConsoleWallet)?; info!( target: LOG_TARGET, @@ -71,7 +71,7 @@ fn main_inner() -> Result<(), ExitCodes> { consts::APP_VERSION ); - debug!(target: LOG_TARGET, "Using configuration: {:?}", config); + debug!(target: LOG_TARGET, "Using configuration: {:?}", global_config); debug!(target: LOG_TARGET, "Using bootstrap: {:?}", bootstrap); // get command line password if provided @@ -82,7 +82,7 @@ fn main_inner() -> Result<(), ExitCodes> { } // check for recovery based on existence of wallet file - let mut boot_mode = boot(&bootstrap, &config)?; + let mut boot_mode = boot(&bootstrap, &global_config)?; let recovery_master_key: Option = get_recovery_master_key(boot_mode, &bootstrap)?; @@ -99,12 +99,12 @@ fn main_inner() -> Result<(), ExitCodes> { if bootstrap.change_password { info!(target: LOG_TARGET, "Change password requested."); - return runtime.block_on(change_password(&config, arg_password, shutdown_signal)); + return runtime.block_on(change_password(&global_config, arg_password, shutdown_signal)); } // initialize wallet let mut wallet = runtime.block_on(init_wallet( - &config, + &global_config, arg_password, seed_words_file_name, recovery_master_key, @@ -118,41 +118,35 @@ fn main_inner() -> Result<(), ExitCodes> { } // get base node/s - let base_node_config = runtime.block_on(get_base_node_peer_config(&config, &mut wallet))?; - let base_node = base_node_config.get_base_node_peer()?; + let base_node_config = runtime.block_on(get_base_node_peer_config(&global_config, &mut wallet))?; + let base_node_selected = base_node_config.get_base_node_peer()?; let wallet_mode = wallet_mode(&bootstrap, boot_mode); // start wallet - runtime.block_on(start_wallet(&mut wallet, &base_node, &wallet_mode))?; + runtime.block_on(start_wallet(&mut wallet, &base_node_selected, &wallet_mode))?; // optional path to notify script - let notify_script = get_notify_script(&bootstrap, &config)?; + let notify_script = get_notify_script(&bootstrap, &global_config)?; debug!(target: LOG_TARGET, "Starting app"); let handle = runtime.handle().clone(); + let config = WalletModeConfig { + base_node_config, + base_node_selected, + daemon_mode: bootstrap.daemon_mode, + global_config, + handle, + notify_script, + wallet_mode: wallet_mode.clone(), + }; let result = match wallet_mode { - WalletMode::Tui => tui_mode( - handle, - config, - wallet.clone(), - base_node, - base_node_config, - notify_script, - ), - WalletMode::Grpc => grpc_mode(handle, wallet.clone(), config), - WalletMode::Script(path) => script_mode(handle, path, wallet.clone(), config), - WalletMode::Command(command) => command_mode(handle, command, wallet.clone(), config), - WalletMode::RecoveryDaemon | WalletMode::RecoveryTui => recovery_mode( - handle, - config, - wallet.clone(), - base_node, - base_node_config, - notify_script, - wallet_mode, - ), + WalletMode::Tui => tui_mode(config, wallet.clone()), + WalletMode::Grpc => grpc_mode(config, wallet.clone()), + WalletMode::Script(path) => script_mode(config, wallet.clone(), path), + WalletMode::Command(command) => command_mode(config, wallet.clone(), command), + WalletMode::RecoveryDaemon | WalletMode::RecoveryTui => recovery_mode(config, wallet.clone()), WalletMode::Invalid => Err(ExitCodes::InputError( "Invalid wallet mode - are you trying too many command options at once?".to_string(), )), diff --git a/applications/tari_console_wallet/src/ui/components/menu.rs b/applications/tari_console_wallet/src/ui/components/menu.rs index 9ebd308ffa..5134b2f969 100644 --- a/applications/tari_console_wallet/src/ui/components/menu.rs +++ b/applications/tari_console_wallet/src/ui/components/menu.rs @@ -1,8 +1,9 @@ use crate::ui::{components::Component, state::AppState}; +use tari_app_utilities::consts; use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, + style::{Color, Style}, text::{Span, Spans}, widgets::{Block, Paragraph}, Frame, @@ -21,45 +22,37 @@ impl Component for Menu { where B: Backend { let columns = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Ratio(4, 5), Constraint::Ratio(1, 5)].as_ref()) + .constraints( + [ + Constraint::Ratio(1, 5), + Constraint::Ratio(3, 5), + Constraint::Ratio(1, 5), + ] + .as_ref(), + ) .split(area); - let others = Spans::from(vec![ - Span::styled("LeftArrow", Style::default().fg(Color::Green)), - Span::styled(":", Style::default().fg(Color::White)), - Span::styled( - " PrevTab ", - Style::default() - .fg(Color::Magenta) - .bg(Color::LightGreen) - .add_modifier(Modifier::BOLD), - ), + let version = Spans::from(vec![ + Span::styled(" Version: ", Style::default().fg(Color::White)), + Span::styled(consts::APP_VERSION_NUMBER, Style::default().fg(Color::Magenta)), + ]); + let tabs = Spans::from(vec![ + Span::styled("LeftArrow: ", Style::default().fg(Color::White)), + Span::styled("Previous Tab ", Style::default().fg(Color::Magenta)), Span::raw(" "), - Span::styled("Tab/RightArrow", Style::default().fg(Color::Green)), - Span::styled(":", Style::default().fg(Color::White)), - Span::styled( - " NextTab ", - Style::default() - .fg(Color::Magenta) - .bg(Color::LightGreen) - .add_modifier(Modifier::BOLD), - ), + Span::styled("Tab/RightArrow: ", Style::default().fg(Color::White)), + Span::styled("Next Tab ", Style::default().fg(Color::Magenta)), ]); let quit = Spans::from(vec![ - Span::styled("F10/Ctrl-Q", Style::default().fg(Color::Green)), - Span::styled(":", Style::default().fg(Color::White)), - Span::styled( - " Quit ", - Style::default() - .fg(Color::Magenta) - .bg(Color::LightGreen) - .add_modifier(Modifier::BOLD), - ), + Span::styled(" F10/Ctrl-Q: ", Style::default().fg(Color::White)), + Span::styled("Quit ", Style::default().fg(Color::Magenta)), ]); - let paragraph1 = Paragraph::new(others).block(Block::default()); - f.render_widget(paragraph1, columns[0]); - let paragraph2 = Paragraph::new(quit).block(Block::default()); - f.render_widget(paragraph2, columns[1]); + let paragraph = Paragraph::new(version).block(Block::default()); + f.render_widget(paragraph, columns[0]); + let paragraph = Paragraph::new(tabs).block(Block::default()); + f.render_widget(paragraph, columns[1]); + let paragraph = Paragraph::new(quit).block(Block::default()); + f.render_widget(paragraph, columns[2]); } } diff --git a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs index 00422ff3fb..f67efc0f92 100644 --- a/applications/tari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/tari_console_wallet/src/ui/components/transactions_tab.rs @@ -6,6 +6,7 @@ use crate::ui::{ widgets::{draw_dialog, MultiColumnList, WindowedListState}, MAX_WIDTH, }; +use chrono::{DateTime, Local}; use tari_crypto::tari_utilities::hex::Hex; use tari_wallet::transaction_service::storage::models::{ CompletedTransaction, @@ -112,8 +113,9 @@ impl TransactionsTab { }; column1_items.push(ListItem::new(Span::styled(format!("{}", t.amount), amount_style))); } + let local_time = DateTime::::from_utc(t.timestamp, Local::now().offset().to_owned()); column2_items.push(ListItem::new(Span::styled( - format!("{}", t.timestamp.format("%Y-%m-%d %H:%M:%S")), + format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), Style::default().fg(text_color), ))); column3_items.push(ListItem::new(Span::styled( @@ -128,7 +130,7 @@ impl TransactionsTab { .max_width(MAX_WIDTH) .add_column(Some("Source/Destination Public Key"), Some(67), column0_items) .add_column(Some("Amount"), Some(18), column1_items) - .add_column(Some("Timestamp"), Some(20), column2_items) + .add_column(Some("Local Date/Time"), Some(20), column2_items) .add_column(Some("Message"), None, column3_items); column_list.render(f, list_areas[0], &mut pending_list_state); @@ -181,8 +183,9 @@ impl TransactionsTab { }; column1_items.push(ListItem::new(Span::styled(format!("{}", t.amount), amount_style))); } + let local_time = DateTime::::from_utc(t.timestamp, Local::now().offset().to_owned()); column2_items.push(ListItem::new(Span::styled( - format!("{}", t.timestamp.format("%Y-%m-%d %H:%M:%S")), + format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), Style::default().fg(text_color), ))); let status = if t.cancelled { @@ -201,7 +204,7 @@ impl TransactionsTab { .max_width(MAX_WIDTH) .add_column(Some("Source/Destination Public Key"), Some(67), column0_items) .add_column(Some("Amount"), Some(18), column1_items) - .add_column(Some("Timestamp"), Some(20), column2_items) + .add_column(Some("Local Date/Time"), Some(20), column2_items) .add_column(Some("Status"), None, column3_items); column_list.render(f, list_areas[1], &mut completed_list_state); @@ -251,7 +254,7 @@ impl TransactionsTab { let fee = Span::styled("Fee:", Style::default().fg(Color::Magenta)); let status = Span::styled("Status:", Style::default().fg(Color::Magenta)); let message = Span::styled("Message:", Style::default().fg(Color::Magenta)); - let timestamp = Span::styled("Timestamp:", Style::default().fg(Color::Magenta)); + let timestamp = Span::styled("Local Date/Time:", Style::default().fg(Color::Magenta)); let excess = Span::styled("Excess:", Style::default().fg(Color::Magenta)); let confirmations = Span::styled("Confirmations:", Style::default().fg(Color::Magenta)); let mined_height = Span::styled("Mined Height:", Style::default().fg(Color::Magenta)); @@ -331,8 +334,9 @@ impl TransactionsTab { }; let status = Span::styled(status_msg, Style::default().fg(Color::White)); let message = Span::styled(tx.message.as_str(), Style::default().fg(Color::White)); + let local_time = DateTime::::from_utc(tx.timestamp, Local::now().offset().to_owned()); let timestamp = Span::styled( - format!("{}", tx.timestamp.format("%Y-%m-%d %H:%M:%S")), + format!("{}", local_time.format("%Y-%m-%d %H:%M:%S")), Style::default().fg(Color::White), ); let excess_hex = if tx.transaction.body.kernels().is_empty() { diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index 9c312c5100..93ab35e008 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -40,7 +40,7 @@ use tui::backend::CrosstermBackend; pub const LOG_TARGET: &str = "wallet::app::main"; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum WalletMode { Tui, Grpc, @@ -51,6 +51,17 @@ pub enum WalletMode { Invalid, } +#[derive(Debug, Clone)] +pub struct WalletModeConfig { + pub base_node_config: PeerConfig, + pub base_node_selected: Peer, + pub daemon_mode: bool, + pub global_config: GlobalConfig, + pub handle: Handle, + pub notify_script: Option, + pub wallet_mode: WalletMode, +} + #[derive(Debug, Clone)] pub struct PeerConfig { pub base_node_custom: Option, @@ -114,21 +125,23 @@ impl PeerConfig { } } -pub fn command_mode( - handle: Handle, - command: String, - wallet: WalletSqlite, - config: GlobalConfig, -) -> Result<(), ExitCodes> { +pub fn command_mode(config: WalletModeConfig, wallet: WalletSqlite, command: String) -> Result<(), ExitCodes> { + let WalletModeConfig { + global_config, handle, .. + } = config.clone(); let commands = vec![parse_command(&command)?]; info!("Starting wallet command mode"); - handle.block_on(command_runner(handle.clone(), commands, wallet, config))?; + handle.block_on(command_runner(handle.clone(), commands, wallet.clone(), global_config))?; + info!("Shutting down wallet command mode"); - Ok(()) + wallet_or_exit(config, wallet) } -pub fn script_mode(handle: Handle, path: PathBuf, wallet: WalletSqlite, config: GlobalConfig) -> Result<(), ExitCodes> { +pub fn script_mode(config: WalletModeConfig, wallet: WalletSqlite, path: PathBuf) -> Result<(), ExitCodes> { + let WalletModeConfig { + global_config, handle, .. + } = config.clone(); info!(target: LOG_TARGET, "Starting wallet script mode"); println!("Starting wallet script mode"); let script = fs::read_to_string(path).map_err(|e| ExitCodes::InputError(e.to_string()))?; @@ -150,32 +163,54 @@ pub fn script_mode(handle: Handle, path: PathBuf, wallet: WalletSqlite, config: println!("{} commands parsed successfully.", commands.len()); println!("Starting the command runner!"); - handle.block_on(command_runner(handle.clone(), commands, wallet, config))?; + handle.block_on(command_runner(handle.clone(), commands, wallet.clone(), global_config))?; info!(target: LOG_TARGET, "Completed wallet script mode"); - Ok(()) + + wallet_or_exit(config, wallet) } -pub fn tui_mode( - handle: Handle, - node_config: GlobalConfig, - wallet: WalletSqlite, - base_node_selected: Peer, - base_node_config: PeerConfig, - notify_script: Option, -) -> Result<(), ExitCodes> { +fn wallet_or_exit(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), ExitCodes> { + debug!(target: LOG_TARGET, "Prompting for run or exit key."); + println!("\nPress Enter to continue to the wallet, or or type q (or quit) followed by Enter."); + let mut buf = String::new(); + std::io::stdin() + .read_line(&mut buf) + .map_err(|e| ExitCodes::IOError(e.to_string()))?; + + match buf.as_str().trim() { + "quit" | "q" | "exit" => Ok(()), + _ => { + if config.daemon_mode { + grpc_mode(config, wallet) + } else { + tui_mode(config, wallet) + } + }, + } +} + +pub fn tui_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), ExitCodes> { + let WalletModeConfig { + base_node_config, + base_node_selected, + global_config, + handle, + notify_script, + .. + } = config; let grpc = WalletGrpcServer::new(wallet.clone()); - handle.spawn(run_grpc(grpc, node_config.grpc_console_wallet_address)); + handle.spawn(run_grpc(grpc, global_config.grpc_console_wallet_address)); let notifier = Notifier::new(notify_script, handle.clone(), wallet.clone()); let app = App::>::new( "Tari Console Wallet".into(), wallet, - node_config.network, + global_config.network, base_node_selected, base_node_config, - node_config, + global_config, notifier, ); @@ -191,15 +226,13 @@ pub fn tui_mode( Ok(()) } -pub fn recovery_mode( - handle: Handle, - config: GlobalConfig, - wallet: WalletSqlite, - base_node_selected: Peer, - base_node_config: PeerConfig, - notify_script: Option, - wallet_mode: WalletMode, -) -> Result<(), ExitCodes> { +pub fn recovery_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), ExitCodes> { + let WalletModeConfig { + base_node_config, + handle, + wallet_mode, + .. + } = config.clone(); println!("Starting recovery..."); match handle.block_on(wallet_recovery(&wallet, &base_node_config)) { Ok(_) => println!("Wallet recovered!"), @@ -217,24 +250,20 @@ pub fn recovery_mode( println!("Starting TUI."); match wallet_mode { - WalletMode::RecoveryDaemon => grpc_mode(handle, wallet, config), - WalletMode::RecoveryTui => tui_mode( - handle, - config, - wallet, - base_node_selected, - base_node_config, - notify_script, - ), + WalletMode::RecoveryDaemon => grpc_mode(config, wallet), + WalletMode::RecoveryTui => tui_mode(config, wallet), _ => Err(ExitCodes::RecoveryError("Unsupported post recovery mode".to_string())), } } -pub fn grpc_mode(handle: Handle, wallet: WalletSqlite, node_config: GlobalConfig) -> Result<(), ExitCodes> { +pub fn grpc_mode(config: WalletModeConfig, wallet: WalletSqlite) -> Result<(), ExitCodes> { + let WalletModeConfig { + global_config, handle, .. + } = config; println!("Starting grpc server"); let grpc = WalletGrpcServer::new(wallet); handle - .block_on(run_grpc(grpc, node_config.grpc_console_wallet_address)) + .block_on(run_grpc(grpc, global_config.grpc_console_wallet_address)) .map_err(ExitCodes::GrpcError)?; println!("Shutting down"); Ok(()) diff --git a/common/src/build/application.rs b/common/src/build/application.rs index 86bfd3b555..c7ab497bdb 100644 --- a/common/src/build/application.rs +++ b/common/src/build/application.rs @@ -55,6 +55,11 @@ impl StaticApplicationInfo { r#"#[allow(dead_code)] pub const APP_VERSION: &str = "{}";"#, self.get_full_version() )?; + writeln!( + file, + r#"#[allow(dead_code)] pub const APP_VERSION_NUMBER: &str = "{}";"#, + self.get_version_number() + )?; writeln!( file, r#"#[allow(dead_code)] pub const APP_AUTHOR: &str = "{}";"#, @@ -72,6 +77,12 @@ impl StaticApplicationInfo { }); format!("{}-{}-{}", self.manifest.package.version, self.commit, build) } + + /// Get the version number only + /// The final output looks like 0.1.2 + fn get_version_number(&self) -> String { + self.manifest.package.version.clone() + } } #[derive(Deserialize)]