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.