From 859b7d3022dab60732f5e638b52f8c1237a2a8f4 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Mon, 6 Jun 2022 12:54:37 +0200 Subject: [PATCH] feat(wallet): add help for wallet cli commands (#4162) Description --- Converts all wallet commands to use clap instead, providing help when called with `--help` * Note: Also commented out the Script mode. I think we can remove this now as it's not used * Note: Deleted all of the previous arg passing logic. * Note: Deleted previous `register_asset` and other asset-y commands Motivation and Context --- This is IMHO a much cleaner and extendible way of calling commands and seeing what the required fields are. It also allows defaults to be added (e.g. default, overrideable messages) How Has This Been Tested? --- manually --- .../tari_app_utilities/src/utilities.rs | 2 +- .../src/automation/command_parser.rs | 651 ------------------ .../src/automation/commands.rs | 488 ++++--------- .../tari_console_wallet/src/automation/mod.rs | 1 - applications/tari_console_wallet/src/cli.rs | 126 +++- .../tari_console_wallet/src/init/mod.rs | 4 +- applications/tari_console_wallet/src/main.rs | 12 +- .../tari_console_wallet/src/wallet_modes.rs | 93 +-- 8 files changed, 329 insertions(+), 1048 deletions(-) delete mode 100644 applications/tari_console_wallet/src/automation/command_parser.rs diff --git a/applications/tari_app_utilities/src/utilities.rs b/applications/tari_app_utilities/src/utilities.rs index 07aa14c28e..c3edfd9560 100644 --- a/applications/tari_app_utilities/src/utilities.rs +++ b/applications/tari_app_utilities/src/utilities.rs @@ -72,7 +72,7 @@ pub fn either_to_node_id(either: Either) -> NodeId { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct UniPublicKey(PublicKey); impl FromStr for UniPublicKey { diff --git a/applications/tari_console_wallet/src/automation/command_parser.rs b/applications/tari_console_wallet/src/automation/command_parser.rs deleted file mode 100644 index 201449f3bd..0000000000 --- a/applications/tari_console_wallet/src/automation/command_parser.rs +++ /dev/null @@ -1,651 +0,0 @@ -// Copyright 2020. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// 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 core::str::SplitWhitespace; -use std::{ - fmt::{Display, Formatter}, - iter::Peekable, - str::FromStr, -}; - -use chrono::{DateTime, Utc}; -use tari_app_utilities::utilities::{parse_emoji_id_or_public_key, parse_hash}; -use tari_common_types::types::PublicKey; -use tari_comms::multiaddr::Multiaddr; -use tari_core::transactions::tari_amount::MicroTari; -use tari_utilities::hex::Hex; - -use crate::automation::{commands::WalletCommand, error::ParseError}; - -#[derive(Debug, Clone)] -pub struct ParsedCommand { - pub command: WalletCommand, - pub args: Vec, -} - -impl Display for ParsedCommand { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - #[allow(clippy::enum_glob_use)] - use WalletCommand::*; - let command = match self.command { - 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", - InitShaAtomicSwap => "init-sha-atomic-swap", - FinaliseShaAtomicSwap => "finalise-sha-atomic-swap", - ClaimShaAtomicSwapRefund => "claim-sha-atomic-swap-refund", - RegisterAsset => "register-asset", - MintTokens => "mint-tokens", - CreateInitialCheckpoint => "create-initial-checkpoint", - CreateCommitteeDefinition => "create-committee-definition", - RevalidateWalletDb => "revalidate-wallet-db", - PublishContractDefinition => "publish-contract-definition", - }; - - let args = self - .args - .iter() - .map(|a| a.to_string()) - .collect::>() - .join(" "); - - write!(f, "{} {}", command, args) - } -} - -#[derive(Debug, Clone)] -pub enum ParsedArgument { - Amount(MicroTari), - PublicKey(PublicKey), - Text(String), - Float(f64), - Int(u64), - Date(DateTime), - OutputToCSVFile(String), - CSVFileName(String), - Address(Multiaddr), - Negotiated(bool), - Hash(Vec), - JSONFileName(String), -} - -impl Display for ParsedArgument { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - #[allow(clippy::enum_glob_use)] - use ParsedArgument::*; - match self { - Amount(v) => write!(f, "{}", v), - PublicKey(v) => write!(f, "{}", v), - Text(v) => write!(f, "{}", v), - Float(v) => write!(f, "{}", v), - Int(v) => write!(f, "{}", v), - Date(v) => write!(f, "{}", v), - OutputToCSVFile(v) => write!(f, "{}", v), - CSVFileName(v) => write!(f, "{}", v), - Address(v) => write!(f, "{}", v), - Negotiated(v) => write!(f, "{}", v), - Hash(v) => write!(f, "{}", v.to_hex()), - JSONFileName(v) => write!(f, "{}", v), - } - } -} - -pub fn parse_command(command: &str) -> Result { - let mut args = command.split_whitespace(); - let command_str = args.next().ok_or_else(|| ParseError::Empty("command".to_string()))?; - - let command = - WalletCommand::from_str(command_str).map_err(|_| ParseError::WalletCommand(command_str.to_string()))?; - - #[allow(clippy::enum_glob_use)] - use WalletCommand::*; - let args = match command { - GetBalance => Vec::new(), - SendTari => parse_send_tari(args)?, - SendOneSided => parse_send_tari(args)?, - MakeItRain => parse_make_it_rain(args)?, - CoinSplit => parse_coin_split(args)?, - DiscoverPeer => parse_public_key(args)?, - Whois => parse_whois(args)?, - ExportUtxos => parse_export_utxos(args)?, - ExportSpentUtxos => parse_export_spent_utxos(args)?, - CountUtxos => Vec::new(), - SetBaseNode => parse_public_key_and_address(args)?, - SetCustomBaseNode => parse_public_key_and_address(args)?, - ClearCustomBaseNode => Vec::new(), - InitShaAtomicSwap => parse_init_sha_atomic_swap(args)?, - FinaliseShaAtomicSwap => parse_finalise_sha_atomic_swap(args)?, - ClaimShaAtomicSwapRefund => parse_claim_htlc_refund_refund(args)?, - RegisterAsset => parser_builder(args).text().build()?, - // mint-tokens pub_key nft_id1 nft_id2 - MintTokens => parser_builder(args).pub_key().text_array().build()?, - CreateInitialCheckpoint => parser_builder(args).pub_key().text().build()?, - CreateCommitteeDefinition => parser_builder(args).pub_key().pub_key_array().build()?, - RevalidateWalletDb => Vec::new(), - PublishContractDefinition => parse_publish_contract_definition(args)?, - }; - - Ok(ParsedCommand { command, args }) -} - -struct ArgParser<'a> { - args: Peekable>, - result: Vec>, -} - -impl<'a> ArgParser<'a> { - fn new(args: SplitWhitespace<'a>) -> Self { - Self { - args: args.peekable(), - result: vec![], - } - } - - fn text(mut self) -> Self { - let text_result = self - .args - .next() - .map(|t| ParsedArgument::Text(t.to_string())) - .ok_or_else(|| ParseError::Empty("text".to_string())); - self.result.push(text_result); - self - } - - fn text_array(self) -> Self { - let mut me = self; - while me.args.peek().is_some() { - me = me.text(); - } - - me - } - - fn pub_key(mut self) -> Self { - // public key/emoji id - let pubkey = self - .args - .next() - .ok_or_else(|| ParseError::Empty("public key or emoji id".to_string())); - let result = pubkey.and_then( - |pb| match parse_emoji_id_or_public_key(pb).ok_or(ParseError::PublicKey) { - Ok(pk) => Ok(ParsedArgument::PublicKey(pk)), - Err(err) => Err(err), - }, - ); - self.result.push(result); - self - } - - fn pub_key_array(self) -> Self { - let mut me = self; - while me.args.peek().is_some() { - me = me.pub_key(); - } - - me - } - - fn build(self) -> Result, ParseError> { - let mut result = Vec::with_capacity(self.result.len()); - for r in self.result { - result.push(r?); - } - Ok(result) - } -} - -fn parser_builder(args: SplitWhitespace) -> ArgParser { - ArgParser::new(args) -} - -fn parse_whois(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)); - - Ok(parsed_args) -} - -fn parse_public_key(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)); - - 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_init_sha_atomic_swap(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - - // amount - let amount = args.next().ok_or_else(|| ParseError::Empty("amount".to_string()))?; - let amount = MicroTari::from_str(amount)?; - parsed_args.push(ParsedArgument::Amount(amount)); - - // 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)); - // message - let message = args.collect::>().join(" "); - parsed_args.push(ParsedArgument::Text(message)); - - Ok(parsed_args) -} - -fn parse_finalise_sha_atomic_swap(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - // hash - let hash = args - .next() - .ok_or_else(|| ParseError::Empty("Output hash".to_string()))?; - let hash = parse_hash(hash).ok_or(ParseError::Hash)?; - parsed_args.push(ParsedArgument::Hash(hash)); - - // public key - let pre_image = args.next().ok_or_else(|| ParseError::Empty("public key".to_string()))?; - let pre_image = parse_emoji_id_or_public_key(pre_image).ok_or(ParseError::PublicKey)?; - parsed_args.push(ParsedArgument::PublicKey(pre_image)); - - Ok(parsed_args) -} - -fn parse_claim_htlc_refund_refund(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - // hash - let hash = args - .next() - .ok_or_else(|| ParseError::Empty("Output hash".to_string()))?; - let hash = parse_hash(hash).ok_or(ParseError::Hash)?; - parsed_args.push(ParsedArgument::Hash(hash)); - - Ok(parsed_args) -} - -fn parse_make_it_rain(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - - // txs per second - let txps = args.next().ok_or_else(|| ParseError::Empty("Txs/s".to_string()))?; - let txps = txps.parse::().map_err(ParseError::Float)?; - if txps > 250.0 { - println!("Maximum transaction rate is 250/sec"); - return Err(ParseError::Invalid("Maximum transaction rate is 250/sec".to_string())); - } - parsed_args.push(ParsedArgument::Float(txps)); - - // duration - let duration = args.next().ok_or_else(|| ParseError::Empty("duration".to_string()))?; - let duration = duration.parse::().map_err(ParseError::Int)?; - parsed_args.push(ParsedArgument::Int(duration)); - - if (txps * duration as f64) < 1.0 { - println!("Invalid data provided for [number of Txs/s] * [test duration (s)], must be >= 1\n"); - return Err(ParseError::Invalid( - "Invalid data provided for [number of Txs/s] * [test duration (s)], must be >= 1".to_string(), - )); - } - - // start amount - let start_amount = args - .next() - .ok_or_else(|| ParseError::Empty("start amount".to_string()))?; - let start_amount = MicroTari::from_str(start_amount)?; - parsed_args.push(ParsedArgument::Amount(start_amount)); - - // increment amount - let inc_amount = args - .next() - .ok_or_else(|| ParseError::Empty("increment amount".to_string()))?; - let inc_amount = MicroTari::from_str(inc_amount)?; - parsed_args.push(ParsedArgument::Amount(inc_amount)); - - // start time utc or 'now' - let start_time = args.next().ok_or_else(|| ParseError::Empty("start time".to_string()))?; - let start_time = if start_time == "now" { - Utc::now() - } else { - DateTime::parse_from_rfc3339(start_time)?.with_timezone(&Utc) - }; - parsed_args.push(ParsedArgument::Date(start_time)); - - // 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)); - - // transaction type - let txn_type = args.next(); - let negotiated = match txn_type { - Some("negotiated") | Some("interactive") => true, - Some("one_sided") | Some("one-sided") | Some("onesided") => false, - _ => { - println!("Invalid data provided for , must be 'interactive' or 'one-sided'\n"); - return Err(ParseError::Invalid( - "Invalid data provided for , must be 'interactive' or 'one-sided'".to_string(), - )); - }, - }; - parsed_args.push(ParsedArgument::Negotiated(negotiated)); - - // message - let message = args.collect::>().join(" "); - parsed_args.push(ParsedArgument::Text(message)); - - Ok(parsed_args) -} - -fn parse_send_tari(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - - // amount - let amount = args.next().ok_or_else(|| ParseError::Empty("amount".to_string()))?; - let amount = MicroTari::from_str(amount)?; - parsed_args.push(ParsedArgument::Amount(amount)); - - // 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)); - - // message - let message = args.collect::>().join(" "); - parsed_args.push(ParsedArgument::Text(message)); - - Ok(parsed_args) -} - -fn parse_export_utxos(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - - if let Some(v) = args.next() { - if v == "--csv-file" { - let file_name = args.next().ok_or_else(|| { - ParseError::Empty( - "file name\n Usage:\n export-utxos\n export-utxos --csv-file ".to_string(), - ) - })?; - parsed_args.push(ParsedArgument::OutputToCSVFile("--csv-file".to_string())); - parsed_args.push(ParsedArgument::CSVFileName(file_name.to_string())); - } else { - return Err(ParseError::Empty( - "'--csv-file' qualifier\n Usage:\n export-utxos\n export-utxos --csv-file " - .to_string(), - )); - } - }; - - Ok(parsed_args) -} - -fn parse_export_spent_utxos(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - - if let Some(v) = args.next() { - if v == "--csv-file" { - let file_name = args.next().ok_or_else(|| { - ParseError::Empty( - "file name\n Usage:\n export-spent-utxos\n export-spent-utxos --csv-file " - .to_string(), - ) - })?; - parsed_args.push(ParsedArgument::OutputToCSVFile("--csv-file".to_string())); - parsed_args.push(ParsedArgument::CSVFileName(file_name.to_string())); - } else { - return Err(ParseError::Empty( - "'--csv-file' qualifier\n Usage:\n export-spent-utxos\n export-spent-utxos --csv-file " - .to_string(), - )); - } - }; - - Ok(parsed_args) -} - -fn parse_coin_split(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = vec![]; - - let amount_per_split = args - .next() - .ok_or_else(|| ParseError::Empty("amount_per_split".to_string()))?; - let amount_per_split = MicroTari::from_str(amount_per_split)?; - parsed_args.push(ParsedArgument::Amount(amount_per_split)); - let num_splits = args - .next() - .ok_or_else(|| ParseError::Empty("split_count".to_string()))?; - let num_splits = num_splits.parse::()?; - let fee_per_gram = args.next().unwrap_or("5"); - parsed_args.push(ParsedArgument::Int(num_splits)); - let fee_per_gram = MicroTari::from_str(fee_per_gram)?; - parsed_args.push(ParsedArgument::Amount(fee_per_gram)); - Ok(parsed_args) -} - -fn parse_publish_contract_definition(mut args: SplitWhitespace) -> Result, ParseError> { - let mut parsed_args = Vec::new(); - - let usage = "Usage:\n publish-contract-definition\n publish-contract-definition --json-file "; - - let arg = args.next().ok_or_else(|| ParseError::Empty("json-file".to_string()))?; - if arg != "--json-file" { - return Err(ParseError::Empty(format!("'--json-file' qualifier\n {}", usage))); - } - - let file_name = args - .next() - .ok_or_else(|| ParseError::Empty(format!("file name\n {}", usage)))?; - parsed_args.push(ParsedArgument::JSONFileName(file_name.to_string())); - - Ok(parsed_args) -} - -#[cfg(test)] -mod test { - use std::str::FromStr; - - use rand::rngs::OsRng; - use tari_common_types::types::PublicKey; - use tari_core::transactions::tari_amount::MicroTari; - use tari_crypto::keys::PublicKey as PublicKeyTrait; - - use crate::automation::{ - command_parser::{parse_command, ParsedArgument}, - error::ParseError, - }; - - #[test] - #[allow(clippy::too_many_lines)] - fn test_parse_command() { - let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - - let command_str = ""; - let parsed = parse_command(command_str); - assert!(parsed.is_err()); - - let command_str = "send-tari asdf"; - let parsed = parse_command(command_str); - assert!(parsed.is_err()); - - let command_str = "send-tari 999T"; - let parsed = parse_command(command_str); - assert!(parsed.is_err()); - - let command_str = "send-tari 999T asdf"; - let parsed = parse_command(command_str); - assert!(parsed.is_err()); - - let command_str = format!("send-tari 999T {} msg text", public_key); - let parsed = parse_command(&command_str).unwrap(); - - if let ParsedArgument::Amount(amount) = parsed.args[0].clone() { - assert_eq!(amount, MicroTari::from_str("999T").unwrap()); - } else { - panic!("Parsed MicroTari amount not the same as provided."); - } - if let ParsedArgument::PublicKey(pk) = parsed.args[1].clone() { - assert_eq!(pk, public_key); - } else { - panic!("Parsed public key is not the same as provided."); - } - if let ParsedArgument::Text(msg) = parsed.args[2].clone() { - assert_eq!(msg, "msg text"); - } else { - panic!("Parsed message is not the same as provided."); - } - - let command_str = format!("send-tari 999ut {}", public_key); - let parsed = parse_command(&command_str).unwrap(); - - if let ParsedArgument::Amount(amount) = parsed.args[0].clone() { - assert_eq!(amount, MicroTari::from_str("999ut").unwrap()); - } else { - panic!("Parsed MicroTari amount not the same as provided."); - } - - let command_str = format!("send-tari 999 {}", public_key); - let parsed = parse_command(&command_str).unwrap(); - - if let ParsedArgument::Amount(amount) = parsed.args[0].clone() { - assert_eq!(amount, MicroTari::from_str("999").unwrap()); - } else { - panic!("Parsed MicroTari amount not the same as provided."); - } - - let command_str = format!("discover-peer {}", public_key); - let parsed = parse_command(&command_str).unwrap(); - - if let ParsedArgument::PublicKey(pk) = parsed.args[0].clone() { - assert_eq!(pk, public_key); - } else { - panic!("Parsed public key is not the same as provided."); - } - - let command_str = "export-utxos --csv-file utxo_list.csv".to_string(); - let parsed = parse_command(&command_str).unwrap(); - - if let ParsedArgument::CSVFileName(file) = parsed.args[1].clone() { - assert_eq!(file, "utxo_list.csv".to_string()); - } else { - panic!("Parsed csv file name is not the same as provided."); - } - - let transaction_type = "negotiated"; - let message = "Testing the network!"; - let command_str = format!( - "make-it-rain 20 225 9000 0 now {} {} {}", - public_key, transaction_type, message - ); - let parsed = parse_command(&command_str).unwrap(); - - if let ParsedArgument::PublicKey(pk) = parsed.args[5].clone() { - assert_eq!(pk, public_key); - } else { - panic!("Parsed public key is not the same as provided."); - } - if let ParsedArgument::Negotiated(negotiated) = parsed.args[6].clone() { - assert!(negotiated); - } else { - panic!("Parsed is not the same as provided."); - } - if let ParsedArgument::Text(msg) = parsed.args[7].clone() { - assert_eq!(message, msg); - } else { - panic!("Parsed message is not the same as provided."); - } - - let transaction_type = "one_sided"; - let command_str = format!( - "make-it-rain 20 225 9000 0 now {} {} {}", - public_key, transaction_type, message - ); - let parsed = parse_command(&command_str).unwrap(); - - if let ParsedArgument::Negotiated(negotiated) = parsed.args[6].clone() { - assert!(!negotiated); - } else { - panic!("Parsed is not the same as provided."); - } - - let transaction_type = "what_ever"; - let command_str = format!( - "make-it-rain 20 225 9000 0 now {} {} {}", - public_key, transaction_type, message - ); - match parse_command(&command_str) { - Ok(_) => panic!(" argument '{}' not allowed", transaction_type), - Err(e) => match e { - ParseError::Invalid(e) => assert_eq!( - e, - "Invalid data provided for , must be 'interactive' or 'one-sided'".to_string() - ), - _ => panic!("Expected parsing to return an error here"), - }, - } - } -} diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index 22e8673d95..c8e8600077 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -23,20 +23,17 @@ use std::{ fs::File, io::{BufReader, LineWriter, Write}, + path::PathBuf, time::{Duration, Instant}, }; -use chrono::Utc; +use chrono::{DateTime, Utc}; use digest::Digest; use futures::FutureExt; use log::*; use sha2::Sha256; use strum_macros::{Display, EnumIter, EnumString}; -use tari_common_types::{ - emoji::EmojiId, - transaction::TxId, - types::{FixedHash, PublicKey}, -}; +use tari_common_types::{emoji::EmojiId, transaction::TxId, types::PublicKey}; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityRequester}, multiaddr::Multiaddr, @@ -47,12 +44,11 @@ use tari_core::transactions::{ tari_amount::{uT, MicroTari, Tari}, transaction_components::{ContractDefinition, TransactionOutput, UnblindedOutput}, }; -use tari_crypto::{keys::PublicKey as PublicKeyTrait, ristretto::pedersen::PedersenCommitmentFactory}; +use tari_crypto::ristretto::pedersen::PedersenCommitmentFactory; use tari_utilities::{hex::Hex, ByteArray, Hashable}; use tari_wallet::{ - assets::{ContractDefinitionFileFormat, KEY_MANAGER_ASSET_BRANCH}, + assets::ContractDefinitionFileFormat, error::WalletError, - key_manager_service::KeyManagerInterface, output_manager_service::handle::OutputManagerHandle, transaction_service::handle::{TransactionEvent, TransactionServiceHandle}, TransactionStage, @@ -66,7 +62,7 @@ use tokio::{ use super::error::CommandError; use crate::{ - automation::command_parser::{ParsedArgument, ParsedCommand}, + cli::CliCommands, utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, }; @@ -103,55 +99,14 @@ pub enum WalletCommand { #[derive(Debug)] pub struct SentTransaction {} -fn get_transaction_parameters(args: Vec) -> Result<(MicroTari, PublicKey, String), CommandError> { - use ParsedArgument::{Amount, PublicKey, Text}; - let amount = match args[0].clone() { - Amount(mtari) => Ok(mtari), - _ => Err(CommandError::Argument), - }?; - - let dest_pubkey = match args[1].clone() { - PublicKey(key) => Ok(key), - _ => Err(CommandError::Argument), - }?; - - let message = match args[2].clone() { - Text(msg) => Ok(msg), - _ => Err(CommandError::Argument), - }?; - - Ok((amount, dest_pubkey, message)) -} - -fn get_init_sha_atomic_swap_parameters( - args: Vec, -) -> Result<(MicroTari, PublicKey, String), CommandError> { - use ParsedArgument::{Amount, PublicKey, Text}; - let amount = match args[0].clone() { - Amount(mtari) => Ok(mtari), - _ => Err(CommandError::Argument), - }?; - - let dest_pubkey = match args[1].clone() { - PublicKey(key) => Ok(key), - _ => Err(CommandError::Argument), - }?; - - let message = match args[2].clone() { - Text(msg) => Ok(msg), - _ => Err(CommandError::Argument), - }?; - - Ok((amount, dest_pubkey, message)) -} - /// Send a normal negotiated transaction to a recipient pub async fn send_tari( mut wallet_transaction_service: TransactionServiceHandle, fee_per_gram: u64, - args: Vec, + amount: MicroTari, + dest_pubkey: PublicKey, + message: String, ) -> Result { - let (amount, dest_pubkey, message) = get_transaction_parameters(args)?; wallet_transaction_service .send_transaction(dest_pubkey, amount, fee_per_gram * uT, message) .await @@ -162,10 +117,10 @@ pub async fn send_tari( pub async fn init_sha_atomic_swap( mut wallet_transaction_service: TransactionServiceHandle, fee_per_gram: u64, - args: Vec, + amount: MicroTari, + dest_pubkey: PublicKey, + message: String, ) -> Result<(TxId, PublicKey, TransactionOutput), CommandError> { - let (amount, dest_pubkey, message) = get_init_sha_atomic_swap_parameters(args)?; - let (tx_id, pre_image, output) = wallet_transaction_service .send_sha_atomic_swap_transaction(dest_pubkey, amount, fee_per_gram * uT, message) .await @@ -177,23 +132,16 @@ pub async fn init_sha_atomic_swap( pub async fn finalise_sha_atomic_swap( mut output_service: OutputManagerHandle, mut transaction_service: TransactionServiceHandle, - args: Vec, + output_hash: Vec, + pre_image: PublicKey, + fee_per_gram: MicroTari, + message: String, ) -> Result { - use ParsedArgument::{Hash, PublicKey}; - let output = match args[0].clone() { - Hash(output) => Ok(output), - _ => Err(CommandError::Argument), - }?; - - let pre_image = match args[1].clone() { - PublicKey(key) => Ok(key), - _ => Err(CommandError::Argument), - }?; let (tx_id, _fee, amount, tx) = output_service - .create_claim_sha_atomic_swap_transaction(output, pre_image, MicroTari(25)) + .create_claim_sha_atomic_swap_transaction(output_hash, pre_image, fee_per_gram) .await?; transaction_service - .submit_transaction(tx_id, tx, amount, "Claimed HTLC atomic swap".into()) + .submit_transaction(tx_id, tx, amount, message) .await?; Ok(tx_id) } @@ -202,19 +150,15 @@ pub async fn finalise_sha_atomic_swap( pub async fn claim_htlc_refund( mut output_service: OutputManagerHandle, mut transaction_service: TransactionServiceHandle, - args: Vec, + output_hash: Vec, + fee_per_gram: MicroTari, + message: String, ) -> Result { - use ParsedArgument::Hash; - let output = match args[0].clone() { - Hash(output) => Ok(output), - _ => Err(CommandError::Argument), - }?; - let (tx_id, _fee, amount, tx) = output_service - .create_htlc_refund_transaction(output, MicroTari(25)) + .create_htlc_refund_transaction(output_hash, fee_per_gram) .await?; transaction_service - .submit_transaction(tx_id, tx, amount, "Claimed HTLC refund".into()) + .submit_transaction(tx_id, tx, amount, message) .await?; Ok(tx_id) } @@ -223,9 +167,10 @@ pub async fn claim_htlc_refund( pub async fn send_one_sided( mut wallet_transaction_service: TransactionServiceHandle, fee_per_gram: u64, - args: Vec, + amount: MicroTari, + dest_pubkey: PublicKey, + message: String, ) -> Result { - let (amount, dest_pubkey, message) = get_transaction_parameters(args)?; wallet_transaction_service .send_one_sided_transaction(dest_pubkey, amount, fee_per_gram * uT, message) .await @@ -233,31 +178,18 @@ pub async fn send_one_sided( } pub async fn coin_split( - args: &[ParsedArgument], + amount_per_split: MicroTari, + num_splits: usize, + fee_per_gram: MicroTari, + message: String, output_service: &mut OutputManagerHandle, transaction_service: &mut TransactionServiceHandle, ) -> Result { - use ParsedArgument::{Amount, Int}; - let amount_per_split = match args[0] { - Amount(s) => Ok(s), - _ => Err(CommandError::Argument), - }?; - - let num_splits = match args[1] { - Int(s) => Ok(s), - _ => Err(CommandError::Argument), - }?; - - let fee_per_gram = match args[2] { - Amount(s) => Ok(s), - _ => Err(CommandError::Argument), - }?; - let (tx_id, tx, amount) = output_service .create_coin_split(amount_per_split, num_splits as usize, fee_per_gram, None) .await?; transaction_service - .submit_transaction(tx_id, tx, amount, "Coin split".into()) + .submit_transaction(tx_id, tx, amount, message) .await?; Ok(tx_id) @@ -288,36 +220,19 @@ async fn wait_for_comms(connectivity_requester: &ConnectivityRequester) -> Resul async fn set_base_node_peer( mut wallet: WalletSqlite, - args: &[ParsedArgument], + public_key: PublicKey, + address: Multiaddr, ) -> 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.clone()) - .await?; - Ok((public_key, net_address)) + println!("{}::{}", public_key, address); + wallet.set_base_node_peer(public_key.clone(), address.clone()).await?; + Ok((public_key, address)) } pub async fn discover_peer( mut dht_service: DhtDiscoveryRequester, - args: Vec, + dest_public_key: PublicKey, ) -> Result<(), CommandError> { - use ParsedArgument::PublicKey; - let dest_public_key = match args[0].clone() { - PublicKey(key) => Ok(key), - _ => Err(CommandError::Argument), - }?; - let start = Instant::now(); println!("🌎 Peer discovery started."); match dht_service @@ -343,50 +258,15 @@ pub async fn discover_peer( pub async fn make_it_rain( wallet_transaction_service: TransactionServiceHandle, fee_per_gram: u64, - args: Vec, + transactions_per_second: u32, + duration: Duration, + start_amount: MicroTari, + increase_amount: MicroTari, + start_time: DateTime, + destination: PublicKey, + negotiated: bool, + message: String, ) -> Result<(), CommandError> { - use ParsedArgument::{Amount, Date, Float, Int, Negotiated, PublicKey, Text}; - - let txps = match args[0].clone() { - Float(r) => Ok(r), - _ => Err(CommandError::Argument), - }?; - - let duration = match args[1].clone() { - Int(s) => Ok(s), - _ => Err(CommandError::Argument), - }?; - - let start_amount = match args[2].clone() { - Amount(mtari) => Ok(mtari), - _ => Err(CommandError::Argument), - }?; - - let inc_amount = match args[3].clone() { - Amount(mtari) => Ok(mtari), - _ => Err(CommandError::Argument), - }?; - - let start_time = match args[4].clone() { - Date(dt) => Ok(dt), - _ => Err(CommandError::Argument), - }?; - - let public_key = match args[5].clone() { - PublicKey(pk) => Ok(pk), - _ => Err(CommandError::Argument), - }?; - - let negotiated = match args[6].clone() { - Negotiated(val) => Ok(val), - _ => Err(CommandError::Argument), - }?; - - let message = match args[7].clone() { - Text(m) => Ok(m), - _ => Err(CommandError::Argument), - }?; - // We are spawning this command in parallel, thus not collecting transaction IDs tokio::task::spawn(async move { // Wait until specified test start time @@ -407,7 +287,7 @@ pub async fn make_it_rain( ); sleep(Duration::from_millis(delay_ms)).await; - let num_txs = (txps * duration as f64) as usize; + let num_txs = (f64::from(transactions_per_second) * duration.as_secs() as f64) as usize; let started_at = Utc::now(); struct TransactionSendStats { @@ -435,15 +315,11 @@ pub async fn make_it_rain( let loop_started_at = Instant::now(); let tx_service = wallet_transaction_service.clone(); // Transaction details - let amount = start_amount + inc_amount * (i as u64); - let send_args = vec![ - ParsedArgument::Amount(amount), - ParsedArgument::PublicKey(public_key.clone()), - ParsedArgument::Text(message.clone()), - ]; + let amount = start_amount + increase_amount * (i as u64); + // Manage transaction submission rate let actual_ms = (Utc::now() - started_at).num_milliseconds(); - let target_ms = (i as f64 / (txps / 1000.0)) as i64; + let target_ms = (i as f64 / f64::from(transactions_per_second) / 1000.0) as i64; if target_ms - actual_ms > 0 { // Maximum delay between Txs set to 120 s sleep(Duration::from_millis((target_ms - actual_ms).min(120_000i64) as u64)).await; @@ -451,13 +327,15 @@ pub async fn make_it_rain( let delayed_for = Instant::now(); let sender_clone = sender.clone(); let fee = fee_per_gram; + let pk = destination.clone(); + let msg = message.clone(); tokio::task::spawn(async move { let spawn_start = Instant::now(); // Send transaction let tx_id = if negotiated { - send_tari(tx_service, fee, send_args).await + send_tari(tx_service, fee, amount, pk.clone(), msg.clone()).await } else { - send_one_sided(tx_service, fee, send_args).await + send_one_sided(tx_service, fee, amount, pk.clone(), msg.clone()).await }; let submit_time = Instant::now(); tokio::task::spawn(async move { @@ -621,7 +499,7 @@ pub async fn monitor_transactions( #[allow(clippy::too_many_lines)] pub async fn command_runner( config: &WalletConfig, - commands: Vec, + commands: Vec, wallet: WalletSqlite, ) -> Result<(), CommandError> { let wait_stage = config.command_send_wait_stage; @@ -639,78 +517,106 @@ pub async fn command_runner( println!("=============="); #[allow(clippy::enum_glob_use)] - use WalletCommand::*; for (idx, parsed) in commands.into_iter().enumerate() { - println!("\n{}. {}\n", idx + 1, parsed); - - match parsed.command { + println!("\n{}. {:?}\n", idx + 1, parsed); + use crate::cli::CliCommands::*; + match parsed { GetBalance => match output_service.clone().get_balance().await { Ok(balance) => { println!("{}", balance); }, Err(e) => eprintln!("GetBalance error! {}", e), }, - DiscoverPeer => { + DiscoverPeer(args) => { if !online { wait_for_comms(&connectivity_requester).await?; online = true; } - discover_peer(dht_service.clone(), parsed.args).await? + discover_peer(dht_service.clone(), args.dest_public_key.into()).await? }, - SendTari => { - let tx_id = send_tari(transaction_service.clone(), config.fee_per_gram, parsed.args).await?; + SendTari(args) => { + let tx_id = send_tari( + transaction_service.clone(), + config.fee_per_gram, + args.amount, + args.destination.into(), + args.message, + ) + .await?; debug!(target: LOG_TARGET, "send-tari tx_id {}", tx_id); tx_ids.push(tx_id); }, - SendOneSided => { - let tx_id = send_one_sided(transaction_service.clone(), config.fee_per_gram, parsed.args).await?; + SendOneSided(args) => { + let tx_id = send_one_sided( + transaction_service.clone(), + config.fee_per_gram, + args.amount, + args.destination.into(), + args.message, + ) + .await?; debug!(target: LOG_TARGET, "send-one-sided tx_id {}", tx_id); tx_ids.push(tx_id); }, - MakeItRain => { - make_it_rain(transaction_service.clone(), config.fee_per_gram, parsed.args).await?; + MakeItRain(args) => { + make_it_rain( + transaction_service.clone(), + config.fee_per_gram, + args.transactions_per_second, + args.duration, + args.start_amount, + args.increase_amount, + args.start_time.unwrap_or_else(Utc::now), + args.destination.into(), + !args.one_sided, + args.message, + ) + .await?; }, - CoinSplit => { - let tx_id = coin_split(&parsed.args, &mut output_service, &mut transaction_service.clone()).await?; + CoinSplit(args) => { + let tx_id = coin_split( + args.amount_per_split, + args.num_splits, + args.fee_per_gram, + args.message, + &mut output_service, + &mut transaction_service.clone(), + ) + .await?; tx_ids.push(tx_id); println!("Coin split succeeded"); }, - Whois => { - let public_key = match parsed.args[0].clone() { - ParsedArgument::PublicKey(key) => Ok(Box::new(key)), - _ => Err(CommandError::Argument), - }?; + Whois(args) => { + let public_key = args.public_key.into(); let emoji_id = EmojiId::from_pubkey(&public_key); println!("Public Key: {}", public_key.to_hex()); println!("Emoji ID : {}", emoji_id); }, - ExportUtxos => { + ExportUtxos(args) => { let utxos = output_service.get_unspent_outputs().await?; let count = utxos.len(); let sum: MicroTari = utxos.iter().map(|utxo| utxo.value).sum(); - if parsed.args.is_empty() { + if let Some(file) = args.output_file { + write_utxos_to_csv_file(utxos, file)?; + } else { for (i, utxo) in utxos.iter().enumerate() { println!("{}. Value: {} {}", i + 1, utxo.value, utxo.features); } - } else if let ParsedArgument::CSVFileName(file) = parsed.args[1].clone() { - write_utxos_to_csv_file(utxos, file)?; - } else { } println!("Total number of UTXOs: {}", count); println!("Total value of UTXOs: {}", sum); }, - ExportSpentUtxos => { + ExportSpentUtxos(args) => { let utxos = output_service.get_spent_outputs().await?; let count = utxos.len(); let sum: MicroTari = utxos.iter().map(|utxo| utxo.value).sum(); - if parsed.args.is_empty() { + if let Some(file) = args.output_file { + write_utxos_to_csv_file(utxos, file)?; + } else { for (i, utxo) in utxos.iter().enumerate() { println!("{}. Value: {} {}", i + 1, utxo.value, utxo.features); } - } else if let ParsedArgument::CSVFileName(file) = parsed.args[1].clone() { - write_utxos_to_csv_file(utxos, file)?; - } else { } println!("Total number of UTXOs: {}", count); println!("Total value of UTXOs: {}", sum); @@ -734,11 +640,12 @@ pub async fn command_runner( println!("Maximum value UTXO : {}", max); } }, - SetBaseNode => { - set_base_node_peer(wallet.clone(), &parsed.args).await?; + SetBaseNode(args) => { + set_base_node_peer(wallet.clone(), args.public_key.into(), args.address).await?; }, - SetCustomBaseNode => { - let (public_key, net_address) = set_base_node_peer(wallet.clone(), &parsed.args).await?; + SetCustomBaseNode(args) => { + let (public_key, net_address) = + set_base_node_peer(wallet.clone(), args.public_key.into(), args.address).await?; wallet .db .set_client_key_value(CUSTOM_BASE_NODE_PUBLIC_KEY_KEY.to_string(), public_key.to_string()) @@ -760,9 +667,15 @@ pub async fn command_runner( .await?; println!("Custom base node peer cleared from wallet database."); }, - InitShaAtomicSwap => { - let (tx_id, pre_image, output) = - init_sha_atomic_swap(transaction_service.clone(), config.fee_per_gram, parsed.clone().args).await?; + InitShaAtomicSwap(args) => { + let (tx_id, pre_image, output) = init_sha_atomic_swap( + transaction_service.clone(), + config.fee_per_gram, + args.amount, + args.destination.into(), + args.message, + ) + .await?; debug!(target: LOG_TARGET, "tari HTLC tx_id {}", tx_id); let hash: [u8; 32] = Sha256::digest(pre_image.as_bytes()).into(); println!("pre_image hex: {}", pre_image.to_hex()); @@ -770,135 +683,32 @@ pub async fn command_runner( println!("Output hash: {}", output.hash().to_hex()); tx_ids.push(tx_id); }, - FinaliseShaAtomicSwap => { - let tx_id = - finalise_sha_atomic_swap(output_service.clone(), transaction_service.clone(), parsed.args).await?; + FinaliseShaAtomicSwap(args) => { + let tx_id = finalise_sha_atomic_swap( + output_service.clone(), + transaction_service.clone(), + args.output_hash[0].clone(), + args.pre_image.into(), + config.fee_per_gram.into(), + args.message, + ) + .await?; debug!(target: LOG_TARGET, "claiming tari HTLC tx_id {}", tx_id); tx_ids.push(tx_id); }, - ClaimShaAtomicSwapRefund => { - let tx_id = claim_htlc_refund(output_service.clone(), transaction_service.clone(), parsed.args).await?; + ClaimShaAtomicSwapRefund(args) => { + let tx_id = claim_htlc_refund( + output_service.clone(), + transaction_service.clone(), + args.output_hash[0].clone(), + config.fee_per_gram.into(), + args.message, + ) + .await?; debug!(target: LOG_TARGET, "claiming tari HTLC tx_id {}", tx_id); tx_ids.push(tx_id); }, - RegisterAsset => { - let name = parsed.args[0].to_string(); - let message = format!("Register asset: {}", name); - let mut manager = wallet.asset_manager.clone(); - let key_manager = wallet.key_manager_service.clone(); - key_manager.add_new_branch(KEY_MANAGER_ASSET_BRANCH).await?; - let result = key_manager.get_next_key(KEY_MANAGER_ASSET_BRANCH).await?; - let public_key = PublicKey::from_secret_key(&result.key); - let public_key_hex = public_key.to_hex(); - println!("Registering asset named: {name}"); - println!("with public key: {public_key_hex}"); - let (tx_id, transaction) = manager - .create_registration_transaction(name, public_key, vec![], None, None, vec![]) - .await?; - transaction_service - .submit_transaction(tx_id, transaction, 0.into(), message) - .await?; - println!("Done!"); - }, - MintTokens => { - println!("Minting tokens for asset"); - let public_key = match parsed.args.get(0) { - Some(ParsedArgument::PublicKey(ref key)) => Ok(key.clone()), - _ => Err(CommandError::Argument), - }?; - - let unique_ids = parsed.args[1..] - .iter() - .map(|arg| { - let s = arg.to_string(); - if let Some(s) = s.strip_prefix("0x") { - Hex::from_hex(s).map_err(|_| CommandError::Argument) - } else { - Ok(s.into_bytes()) - } - }) - .collect::>, _>>()?; - - let mut asset_manager = wallet.asset_manager.clone(); - let asset = asset_manager.get_owned_asset_by_pub_key(&public_key).await?; - println!("Asset name: {}", asset.name()); - - let message = format!("Minting {} tokens for asset {}", unique_ids.len(), asset.name()); - let (tx_id, transaction) = asset_manager - .create_minting_transaction( - &public_key, - asset.owner_commitment(), - unique_ids.into_iter().map(|id| (id, None)).collect(), - ) - .await?; - transaction_service - .submit_transaction(tx_id, transaction, 0.into(), message) - .await?; - }, - CreateInitialCheckpoint => { - println!("Creating Initial Checkpoint for Asset"); - let asset_public_key = match parsed.args.get(0) { - Some(ParsedArgument::PublicKey(ref key)) => Ok(key.clone()), - _ => Err(CommandError::Argument), - }?; - - let merkle_root = match parsed.args.get(1) { - Some(ParsedArgument::Text(ref root)) => match &root[0..2] { - "0x" => FixedHash::from_hex(&root[2..]).map_err(|_| CommandError::Argument)?, - _ => FixedHash::from_hex(root).map_err(|_| CommandError::Argument)?, - }, - _ => return Err(CommandError::Argument), - }; - - let message = format!("Initial asset checkpoint for {}", asset_public_key); - - let mut asset_manager = wallet.asset_manager.clone(); - let (tx_id, transaction) = asset_manager - .create_initial_asset_checkpoint(&asset_public_key, merkle_root) - .await?; - transaction_service - .submit_transaction(tx_id, transaction, 0.into(), message) - .await?; - }, - CreateCommitteeDefinition => { - let asset_public_key = match parsed.args.get(0) { - Some(ParsedArgument::PublicKey(ref key)) => Ok(key.clone()), - _ => Err(CommandError::Argument), - }?; - let public_key_hex = asset_public_key.to_hex(); - println!("Creating Committee Definition for Asset"); - println!("with public key {public_key_hex}"); - - let committee_public_keys: Vec = parsed.args[1..] - .iter() - .map(|pk| match pk { - ParsedArgument::PublicKey(ref key) => Ok(key.clone()), - _ => Err(CommandError::Argument), - }) - .collect::>()?; - - let num_members = committee_public_keys.len(); - if num_members < 1 { - return Err(CommandError::Config("Committee has no members!".into())); - } - let message = format!( - "Committee definition with {} members for {}", - num_members, asset_public_key - ); - println!("with {num_members} committee members"); - let mut asset_manager = wallet.asset_manager.clone(); - // todo: effective sidechain height... - // todo: updating - let (tx_id, transaction) = asset_manager - .create_committee_definition(&asset_public_key, &committee_public_keys, 0, true) - .await?; - - transaction_service - .submit_transaction(tx_id, transaction, 0.into(), message) - .await?; - println!("Done!"); - }, RevalidateWalletDb => { output_service .revalidate_all_outputs() @@ -909,13 +719,9 @@ pub async fn command_runner( .await .map_err(CommandError::TransactionServiceError)?; }, - PublishContractDefinition => { + PublishContractDefinition(args) => { // open the JSON file with the contract definition values - let file_path = match parsed.args.get(0) { - Some(ParsedArgument::JSONFileName(ref file_path)) => Ok(file_path), - _ => Err(CommandError::Argument), - }?; - let file = File::open(file_path).map_err(|e| CommandError::JSONFile(e.to_string()))?; + let file = File::open(&args.file_path).map_err(|e| CommandError::JSONFile(e.to_string()))?; let file_reader = BufReader::new(file); // parse the JSON file @@ -983,7 +789,7 @@ pub async fn command_runner( Ok(()) } -fn write_utxos_to_csv_file(utxos: Vec, file_path: String) -> Result<(), CommandError> { +fn write_utxos_to_csv_file(utxos: Vec, file_path: PathBuf) -> Result<(), CommandError> { let factory = PedersenCommitmentFactory::default(); let file = File::create(file_path).map_err(|e| CommandError::CSVFile(e.to_string()))?; let mut csv_file = LineWriter::new(file); diff --git a/applications/tari_console_wallet/src/automation/mod.rs b/applications/tari_console_wallet/src/automation/mod.rs index 590a889e34..dd897852e7 100644 --- a/applications/tari_console_wallet/src/automation/mod.rs +++ b/applications/tari_console_wallet/src/automation/mod.rs @@ -20,6 +20,5 @@ // 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. -pub mod command_parser; pub mod commands; pub mod error; diff --git a/applications/tari_console_wallet/src/cli.rs b/applications/tari_console_wallet/src/cli.rs index 9c686fe038..6a023cd59d 100644 --- a/applications/tari_console_wallet/src/cli.rs +++ b/applications/tari_console_wallet/src/cli.rs @@ -20,10 +20,14 @@ // 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 std::path::PathBuf; +use std::{path::PathBuf, time::Duration}; -use clap::Parser; -use tari_app_utilities::common_cli_args::CommonCliArgs; +use chrono::{DateTime, Utc}; +use clap::{Args, Parser, Subcommand}; +use tari_app_utilities::{common_cli_args::CommonCliArgs, utilities::UniPublicKey}; +use tari_comms::multiaddr::Multiaddr; +use tari_core::transactions::{tari_amount, tari_amount::MicroTari}; +use tari_utilities::hex::{Hex, HexError}; const DEFAULT_NETWORK: &str = "dibbler"; @@ -72,6 +76,8 @@ pub(crate) struct Cli { /// Supply a network (overrides existing configuration) #[clap(long, default_value = DEFAULT_NETWORK, env = "TARI_NETWORK")] pub network: String, + #[clap(subcommand)] + pub command2: Option, } impl Cli { @@ -82,3 +88,117 @@ impl Cli { overrides } } + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Subcommand, Clone)] +pub enum CliCommands { + GetBalance, + SendTari(SendTariArgs), + SendOneSided(SendTariArgs), + MakeItRain(MakeItRainArgs), + CoinSplit(CoinSplitArgs), + DiscoverPeer(DiscoverPeerArgs), + Whois(WhoisArgs), + ExportUtxos(ExportUtxosArgs), + ExportSpentUtxos(ExportUtxosArgs), + CountUtxos, + SetBaseNode(SetBaseNodeArgs), + SetCustomBaseNode(SetBaseNodeArgs), + ClearCustomBaseNode, + InitShaAtomicSwap(SendTariArgs), + FinaliseShaAtomicSwap(FinaliseShaAtomicSwapArgs), + ClaimShaAtomicSwapRefund(ClaimShaAtomicSwapRefundArgs), + RevalidateWalletDb, + PublishContractDefinition(PublishContractDefinitionArgs), +} + +#[derive(Debug, Args, Clone)] +pub struct DiscoverPeerArgs { + pub dest_public_key: UniPublicKey, +} + +#[derive(Debug, Args, Clone)] +pub struct SendTariArgs { + pub amount: MicroTari, + pub destination: UniPublicKey, + #[clap(short, long, default_value = "")] + pub message: String, +} + +#[derive(Debug, Args, Clone)] +pub struct MakeItRainArgs { + pub destination: UniPublicKey, + #[clap(short, long, alias="amount", default_value_t = tari_amount::T)] + pub start_amount: MicroTari, + #[clap(short, long, alias = "tps", default_value_t = 25)] + pub transactions_per_second: u32, + #[clap(short, long, parse(try_from_str = parse_duration), default_value="60")] + pub duration: Duration, + #[clap(long, default_value_t=tari_amount::T)] + pub increase_amount: MicroTari, + #[clap(long)] + pub start_time: Option>, + #[clap(short, long)] + pub one_sided: bool, + #[clap(short, long, default_value = "Make it rain")] + pub message: String, +} + +fn parse_duration(arg: &str) -> Result { + let seconds = arg.parse()?; + Ok(std::time::Duration::from_secs(seconds)) +} + +#[derive(Debug, Args, Clone)] +pub struct CoinSplitArgs { + pub amount_per_split: MicroTari, + pub num_splits: usize, + #[clap(short, long, default_value = "1")] + pub fee_per_gram: MicroTari, + #[clap(short, long, default_value = "Coin split")] + pub message: String, +} + +#[derive(Debug, Args, Clone)] +pub struct WhoisArgs { + pub public_key: UniPublicKey, +} + +#[derive(Debug, Args, Clone)] +pub struct ExportUtxosArgs { + #[clap(short, long)] + pub output_file: Option, +} + +#[derive(Debug, Args, Clone)] +pub struct SetBaseNodeArgs { + pub public_key: UniPublicKey, + pub address: Multiaddr, +} + +#[derive(Debug, Args, Clone)] +pub struct FinaliseShaAtomicSwapArgs { + #[clap(short, long, parse(try_from_str = parse_hex), required=true )] + pub output_hash: Vec>, + #[clap(short, long)] + pub pre_image: UniPublicKey, + #[clap(short, long, default_value = "Claimed HTLC atomic swap")] + pub message: String, +} + +fn parse_hex(s: &str) -> Result, HexError> { + Vec::::from_hex(s) +} + +#[derive(Debug, Args, Clone)] +pub struct ClaimShaAtomicSwapRefundArgs { + #[clap(short, long, parse(try_from_str = parse_hex), required=true )] + pub output_hash: Vec>, + #[clap(short, long, default_value = "Claimed HTLC atomic swap refund")] + pub message: String, +} + +#[derive(Debug, Args, Clone)] +pub struct PublishContractDefinitionArgs { + pub file_path: PathBuf, +} diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index ce74dc81ed..4aae8be29c 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -202,7 +202,7 @@ pub(crate) fn wallet_mode(cli: &Cli, boot_mode: WalletBoot) -> WalletMode { } } - match (cli.non_interactive_mode, cli.input_file.clone(), cli.command.clone()) { + match (cli.non_interactive_mode, cli.input_file.clone(), cli.command2.clone()) { // TUI mode (false, None, None) => WalletMode::Tui, // GRPC mode @@ -210,7 +210,7 @@ pub(crate) fn wallet_mode(cli: &Cli, boot_mode: WalletBoot) -> WalletMode { // Script mode (_, Some(path), None) => WalletMode::Script(path), // Command mode - (_, None, Some(command)) => WalletMode::Command(command), + (_, None, Some(command)) => WalletMode::Command(Box::new(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 ed26a779c1..9b737f6ca9 100644 --- a/applications/tari_console_wallet/src/main.rs +++ b/applications/tari_console_wallet/src/main.rs @@ -190,9 +190,15 @@ fn main_inner() -> Result<(), ExitError> { WalletMode::Tui => tui_mode(handle, &config.wallet, &base_node_config, wallet.clone()), WalletMode::Grpc => grpc_mode(handle, &config.wallet, wallet.clone()), WalletMode::Script(path) => script_mode(handle, &cli, &config.wallet, &base_node_config, wallet.clone(), path), - WalletMode::Command(command) => { - command_mode(handle, &cli, &config.wallet, &base_node_config, wallet.clone(), command) - }, + WalletMode::Command(command) => command_mode( + handle, + &cli, + &config.wallet, + &base_node_config, + wallet.clone(), + *command, + ), + WalletMode::RecoveryDaemon | WalletMode::RecoveryTui => { recovery_mode(handle, &base_node_config, &config.wallet, wallet_mode, wallet.clone()) }, diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index f5e9759c0e..2996fc1060 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -19,7 +19,7 @@ // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // 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 std::{fs, io::Stdout, path::PathBuf}; +use std::{io::Stdout, path::PathBuf}; use log::*; use rand::{rngs::OsRng, seq::SliceRandom}; @@ -31,8 +31,8 @@ use tonic::transport::Server; use tui::backend::CrosstermBackend; use crate::{ - automation::{command_parser::parse_command, commands::command_runner}, - cli::Cli, + automation::commands::command_runner, + cli::{Cli, CliCommands}, grpc::WalletGrpcServer, notifier::Notifier, recovery::wallet_recovery, @@ -48,7 +48,7 @@ pub enum WalletMode { Tui, Grpc, Script(PathBuf), - Command(String), + Command(Box), RecoveryDaemon, RecoveryTui, Invalid, @@ -136,9 +136,9 @@ pub(crate) fn command_mode( config: &WalletConfig, base_node_config: &PeerConfig, wallet: WalletSqlite, - command: String, + command: CliCommands, ) -> Result<(), ExitError> { - let commands = vec![parse_command(&command)?]; + let commands = vec![command]; // Do not remove this println! const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (Command mode started)"; @@ -157,47 +157,48 @@ pub(crate) fn command_mode( } pub(crate) fn script_mode( - handle: Handle, - cli: &Cli, - config: &WalletConfig, - base_node_config: &PeerConfig, - wallet: WalletSqlite, - path: PathBuf, + _handle: Handle, + _cli: &Cli, + _config: &WalletConfig, + _base_node_config: &PeerConfig, + _wallet: WalletSqlite, + _path: PathBuf, ) -> Result<(), ExitError> { - info!(target: LOG_TARGET, "Starting wallet script mode"); - println!("Starting wallet script mode"); - let script = fs::read_to_string(path).map_err(|e| ExitError::new(ExitCode::InputError, e))?; - - if script.is_empty() { - return Err(ExitError::new(ExitCode::InputError, "Input file is empty!")); - }; - - let mut commands = Vec::new(); - - println!("Parsing commands..."); - for command in script.lines() { - // skip empty lines and 'comments' starting with # - if !command.is_empty() && !command.starts_with('#') { - // parse the command - commands.push(parse_command(command)?); - } - } - println!("{} commands parsed successfully.", commands.len()); - - // Do not remove this println! - const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (Script mode started)"; - println!("{}", CUCUMBER_TEST_MARKER_A); - - println!("Starting the command runner!"); - handle.block_on(command_runner(config, commands, wallet.clone()))?; - - // Do not remove this println! - const CUCUMBER_TEST_MARKER_B: &str = "Tari Console Wallet running... (Script mode completed)"; - println!("{}", CUCUMBER_TEST_MARKER_B); - - info!(target: LOG_TARGET, "Completed wallet script mode"); - - wallet_or_exit(handle, cli, config, base_node_config, wallet) + todo!("To be removed") + // info!(target: LOG_TARGET, "Starting wallet script mode"); + // println!("Starting wallet script mode"); + // let script = fs::read_to_string(path).map_err(|e| ExitError::new(ExitCode::InputError, e))?; + // + // if script.is_empty() { + // return Err(ExitError::new(ExitCode::InputError, "Input file is empty!")); + // }; + // + // let mut commands = Vec::new(); + // + // println!("Parsing commands..."); + // for command in script.lines() { + // // skip empty lines and 'comments' starting with # + // if !command.is_empty() && !command.starts_with('#') { + // // parse the command + // commands.push(parse_command(command)?); + // } + // } + // println!("{} commands parsed successfully.", commands.len()); + // + // // Do not remove this println! + // const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (Script mode started)"; + // println!("{}", CUCUMBER_TEST_MARKER_A); + // + // println!("Starting the command runner!"); + // handle.block_on(command_runner(config, commands, wallet.clone()))?; + // + // // Do not remove this println! + // const CUCUMBER_TEST_MARKER_B: &str = "Tari Console Wallet running... (Script mode completed)"; + // println!("{}", CUCUMBER_TEST_MARKER_B); + // + // info!(target: LOG_TARGET, "Completed wallet script mode"); + // + // wallet_or_exit(handle, cli, config, base_node_config, wallet) } /// Prompts the user to continue to the wallet, or exit.