diff --git a/bin/drk/src/cli_util.rs b/bin/drk/src/cli_util.rs index 7c232db70..7c42f696d 100644 --- a/bin/drk/src/cli_util.rs +++ b/bin/drk/src/cli_util.rs @@ -17,7 +17,6 @@ */ use std::{ io::{stdin, Cursor, Read}, - process::exit, str::FromStr, }; @@ -39,28 +38,37 @@ use crate::{money::BALANCE_BASE10_DECIMALS, Drk}; pub async fn parse_tx_from_stdin() -> Result { let mut buf = String::new(); stdin().read_to_string(&mut buf)?; - let Some(bytes) = base64::decode(buf.trim()) else { - eprintln!("Failed to decode transaction"); - exit(2); - }; + match base64::decode(buf.trim()) { + Some(bytes) => Ok(deserialize_async(&bytes).await?), + None => Err(Error::ParseFailed("Failed to decode transaction")), + } +} - Ok(deserialize_async(&bytes).await?) +/// Auxiliary function to parse a base64 encoded transaction from +/// provided input or fallback to stdin if its empty. +pub async fn parse_tx_from_input(input: &[String]) -> Result { + match input.len() { + 0 => parse_tx_from_stdin().await, + 1 => match base64::decode(input[0].trim()) { + Some(bytes) => Ok(deserialize_async(&bytes).await?), + None => Err(Error::ParseFailed("Failed to decode transaction")), + }, + _ => Err(Error::ParseFailed("Multiline input provided")), + } } /// Auxiliary function to parse provided string into a values pair. pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> { let v: Vec<&str> = s.split(':').collect(); if v.len() != 2 { - eprintln!("Invalid value pair. Use a pair such as 13.37:11.0"); - exit(2); + return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0")) } let val0 = decode_base10(v[0], BALANCE_BASE10_DECIMALS, true); let val1 = decode_base10(v[1], BALANCE_BASE10_DECIMALS, true); if val0.is_err() || val1.is_err() { - eprintln!("Invalid value pair. Use a pair such as 13.37:11.0"); - exit(2); + return Err(Error::ParseFailed("Invalid value pair. Use a pair such as 13.37:11.0")) } Ok((val0.unwrap(), val1.unwrap())) @@ -70,22 +78,20 @@ pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> { pub async fn parse_token_pair(drk: &Drk, s: &str) -> Result<(TokenId, TokenId)> { let v: Vec<&str> = s.split(':').collect(); if v.len() != 2 { - eprintln!("Invalid token pair. Use a pair such as:"); - eprintln!("WCKD:MLDY"); - eprintln!("or"); - eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"); - exit(2); + return Err(Error::ParseFailed( + "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\ + A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2" + )) } let tok0 = drk.get_token(v[0].to_string()).await; let tok1 = drk.get_token(v[1].to_string()).await; if tok0.is_err() || tok1.is_err() { - eprintln!("Invalid token pair. Use a pair such as:"); - eprintln!("WCKD:MLDY"); - eprintln!("or"); - eprintln!("A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2"); - exit(2); + return Err(Error::ParseFailed( + "Invalid token pair. Use a pair such as:\nWCKD:MLDY\nor\n\ + A7f1RKsCUUHrSXA7a9ogmwg8p3bs6F47ggsW826HD4yd:FCuoMii64H5Ee4eVWBjP18WTFS8iLUJmGi16Qti1xFQ2" + )) } Ok((tok0.unwrap(), tok1.unwrap())) @@ -107,7 +113,7 @@ pub async fn kaching() { } /// Auxiliary function to generate provided shell completions. -pub fn generate_completions(shell: &str) -> Result<()> { +pub fn generate_completions(shell: &str) -> Result { // Sub-commands // Interactive @@ -537,7 +543,15 @@ pub fn generate_completions(shell: &str) -> Result<()> { Err(e) => return Err(Error::Custom(e)), }; - app.gen_completions_to("./drk", shell, &mut std::io::stdout()); + let mut buf = vec![]; + app.gen_completions_to("./drk", shell, &mut buf); - Ok(()) + Ok(String::from_utf8(buf)?) +} + +/// Auxiliary function to generate provided shell completions. +pub fn print_output(buf: &[String]) { + for line in buf { + println!("{line}"); + } } diff --git a/bin/drk/src/dao.rs b/bin/drk/src/dao.rs index b93f19380..a4d04fa53 100644 --- a/bin/drk/src/dao.rs +++ b/bin/drk/src/dao.rs @@ -1631,24 +1631,26 @@ impl Drk { } /// Reset the DAO Merkle trees in the cache. - pub fn reset_dao_trees(&self) -> WalletDbResult<()> { - println!("Resetting DAO Merkle trees"); + pub fn reset_dao_trees(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting DAO Merkle trees")); if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_DAO_DAOS) { - println!("[reset_dao_trees] Resetting DAO DAOs Merkle tree failed: {e:?}"); + output.push(format!("[reset_dao_trees] Resetting DAO DAOs Merkle tree failed: {e:?}")); return Err(WalletDbError::GenericError) } if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_DAO_PROPOSALS) { - println!("[reset_dao_trees] Resetting DAO Proposals Merkle tree failed: {e:?}"); + output.push(format!( + "[reset_dao_trees] Resetting DAO Proposals Merkle tree failed: {e:?}" + )); return Err(WalletDbError::GenericError) } - println!("Successfully reset DAO Merkle trees"); + output.push(String::from("Successfully reset DAO Merkle trees")); Ok(()) } /// Reset confirmed DAOs in the wallet. - pub fn reset_daos(&self) -> WalletDbResult<()> { - println!("Resetting DAO confirmations"); + pub fn reset_daos(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting DAO confirmations")); let query = format!( "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL;", *DAO_DAOS_TABLE, @@ -1658,15 +1660,19 @@ impl Drk { DAO_DAOS_COL_CALL_INDEX, ); self.wallet.exec_sql(&query, &[])?; - println!("Successfully unconfirmed DAOs"); + output.push(String::from("Successfully unconfirmed DAOs")); Ok(()) } /// Reset confirmed DAOs in the wallet that were minted after /// provided height. - pub fn unconfirm_daos_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Resetting DAO confirmations after: {height}"); + pub fn unconfirm_daos_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Resetting DAO confirmations after: {height}")); let query = format!( "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} > ?1;", *DAO_DAOS_TABLE, @@ -1677,14 +1683,14 @@ impl Drk { DAO_DAOS_COL_MINT_HEIGHT, ); self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; - println!("Successfully unconfirmed DAOs"); + output.push(String::from("Successfully unconfirmed DAOs")); Ok(()) } /// Reset all DAO proposals in the wallet. - pub fn reset_dao_proposals(&self) -> WalletDbResult<()> { - println!("Resetting DAO proposals confirmations"); + pub fn reset_dao_proposals(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting DAO proposals confirmations")); let query = format!( "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL;", *DAO_PROPOSALS_TABLE, @@ -1698,15 +1704,19 @@ impl Drk { DAO_PROPOSALS_COL_EXEC_TX_HASH, ); self.wallet.exec_sql(&query, &[])?; - println!("Successfully unconfirmed DAO proposals"); + output.push(String::from("Successfully unconfirmed DAO proposals")); Ok(()) } /// Reset DAO proposals in the wallet that were minted after /// provided height. - pub fn unconfirm_dao_proposals_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Resetting DAO proposals confirmations after: {height}"); + pub fn unconfirm_dao_proposals_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Resetting DAO proposals confirmations after: {height}")); let query = format!( "UPDATE {} SET {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL, {} = NULL WHERE {} > ?1;", *DAO_PROPOSALS_TABLE, @@ -1721,15 +1731,19 @@ impl Drk { DAO_PROPOSALS_COL_MINT_HEIGHT, ); self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; - println!("Successfully unconfirmed DAO proposals"); + output.push(String::from("Successfully unconfirmed DAO proposals")); Ok(()) } /// Reset execution information in the wallet for DAO proposals /// that were executed after provided height. - pub fn unexec_dao_proposals_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Resetting DAO proposals execution information after: {height}"); + pub fn unexec_dao_proposals_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Resetting DAO proposals execution information after: {height}")); let query = format!( "UPDATE {} SET {} = NULL, {} = NULL WHERE {} > ?1;", *DAO_PROPOSALS_TABLE, @@ -1738,29 +1752,33 @@ impl Drk { DAO_PROPOSALS_COL_EXEC_HEIGHT, ); self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; - println!("Successfully reset DAO proposals execution information"); + output.push(String::from("Successfully reset DAO proposals execution information")); Ok(()) } /// Reset all DAO votes in the wallet. - pub fn reset_dao_votes(&self) -> WalletDbResult<()> { - println!("Resetting DAO votes"); + pub fn reset_dao_votes(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting DAO votes")); let query = format!("DELETE FROM {};", *DAO_VOTES_TABLE); self.wallet.exec_sql(&query, &[])?; - println!("Successfully reset DAO votes"); + output.push(String::from("Successfully reset DAO votes")); Ok(()) } /// Remove the DAO votes in the wallet that were created after /// provided height. - pub fn remove_dao_votes_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Removing DAO votes after: {height}"); + pub fn remove_dao_votes_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Removing DAO votes after: {height}")); let query = format!("DELETE FROM {} WHERE {} > ?1;", *DAO_VOTES_TABLE, DAO_VOTES_COL_BLOCK_HEIGHT); self.wallet.exec_sql(&query, rusqlite::params![height])?; - println!("Successfully removed DAO votes"); + output.push(String::from("Successfully removed DAO votes")); Ok(()) } diff --git a/bin/drk/src/deploy.rs b/bin/drk/src/deploy.rs index 0f6646201..4ea2776e9 100644 --- a/bin/drk/src/deploy.rs +++ b/bin/drk/src/deploy.rs @@ -85,22 +85,26 @@ impl Drk { } /// Reset all token deploy authorities frozen status in the wallet. - pub fn reset_deploy_authorities(&self) -> WalletDbResult<()> { - println!("Resetting deploy authorities frozen status"); + pub fn reset_deploy_authorities(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting deploy authorities frozen status")); let query = format!( "UPDATE {} SET {} = 0, {} = NULL;", *DEPLOY_AUTH_TABLE, DEPLOY_AUTH_COL_IS_FROZEN, DEPLOY_AUTH_COL_FREEZE_HEIGHT ); self.wallet.exec_sql(&query, &[])?; - println!("Successfully reset deploy authorities frozen status"); + output.push(String::from("Successfully reset deploy authorities frozen status")); Ok(()) } /// Remove deploy authorities frozen status in the wallet that /// where frozen after provided height. - pub fn unfreeze_deploy_authorities_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Resetting deploy authorities frozen status after: {height}"); + pub fn unfreeze_deploy_authorities_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Resetting deploy authorities frozen status after: {height}")); let query = format!( "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;", *DEPLOY_AUTH_TABLE, @@ -109,7 +113,7 @@ impl Drk { DEPLOY_AUTH_COL_FREEZE_HEIGHT ); self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; - println!("Successfully reset deploy authorities frozen status"); + output.push(String::from("Successfully reset deploy authorities frozen status")); Ok(()) } diff --git a/bin/drk/src/interactive.rs b/bin/drk/src/interactive.rs index d4e488f08..094160b0f 100644 --- a/bin/drk/src/interactive.rs +++ b/bin/drk/src/interactive.rs @@ -47,7 +47,8 @@ use darkfi_serial::{deserialize_async, serialize_async}; use crate::{ cli_util::{ - generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair, + generate_completions, kaching, parse_token_pair, parse_tx_from_input, parse_value_pair, + print_output, }, money::BALANCE_BASE10_DECIMALS, rpc::subscribe_blocks, @@ -293,15 +294,8 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: & let commands: Vec<&str> = line.split('|').collect(); // Process each command - let mut input = vec![]; + let mut output = vec![]; for command in commands { - // Handle previous command output - // TODO: this is examplatory, we will actually use the input - // in our commands - for line in &input { - println!("{line}"); - } - // Parse command parts let parts: Vec<&str> = command.split_whitespace().collect(); if parts.is_empty() { @@ -309,20 +303,21 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: & } // Handle command - let mut output = vec![]; + let input = output; + output = vec![]; match parts[0] { "help" => help(&mut output), "kaching" => kaching().await, - "ping" => handle_ping(drk).await, - "completions" => handle_completions(&parts), - "wallet" => handle_wallet(drk, &parts).await, - "spend" => handle_spend(drk).await, - "unspend" => handle_unspend(drk, &parts).await, - "transfer" => handle_transfer(drk, &parts).await, - "otc" => handle_otc(drk, &parts).await, - "attach-fee" => handle_attach_fee(drk).await, - "inspect" => handle_inspect().await, - "broadcast" => handle_broadcast(drk).await, + "ping" => handle_ping(drk, &mut output).await, + "completions" => handle_completions(&parts, &mut output), + "wallet" => handle_wallet(drk, &parts, &input, &mut output).await, + "spend" => handle_spend(drk, &input, &mut output).await, + "unspend" => handle_unspend(drk, &parts, &mut output).await, + "transfer" => handle_transfer(drk, &parts, &mut output).await, + "otc" => handle_otc(drk, &parts, &input, &mut output).await, + "attach-fee" => handle_attach_fee(drk, &input, &mut output).await, + "inspect" => handle_inspect(&input, &mut output).await, + "broadcast" => handle_broadcast(drk, &input, &mut output).await, "subscribe" => { handle_subscribe( drk, @@ -331,24 +326,23 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: & &subscription_task, &shell_sender, ex, + &mut output, ) .await } "unsubscribe" => { - handle_unsubscribe(&mut subscription_active, &subscription_task).await + handle_unsubscribe(&mut subscription_active, &subscription_task, &mut output) + .await } "snooze" => snooze_active = true, "unsnooze" => snooze_active = false, - "scan" => handle_scan(drk, &subscription_active, &parts).await, - _ => println!("Unreconized command: {}", parts[0]), + "scan" => handle_scan(drk, &subscription_active, &parts, &mut output).await, + _ => output.push(format!("Unreconized command: {}", parts[0])), } - input = output } // Handle last command output - for line in input { - println!("{line}"); - } + print_output(&output); } // Stop the subscription task if its active @@ -461,88 +455,89 @@ async fn listen_for_line( } /// Auxiliary function to define the ping command handling. -async fn handle_ping(drk: &DrkPtr) { - if let Err(e) = drk.read().await.ping().await { - println!("Error while executing ping command: {e}") +async fn handle_ping(drk: &DrkPtr, output: &mut Vec) { + if let Err(e) = drk.read().await.ping(output).await { + output.push(format!("Error while executing ping command: {e}")) } } /// Auxiliary function to define the completions command handling. -fn handle_completions(parts: &[&str]) { +fn handle_completions(parts: &[&str], output: &mut Vec) { // Check correct command structure if parts.len() != 2 { - println!("Malformed `completions` command"); - println!("Usage: completions "); + output.push(String::from("Malformed `completions` command")); + output.push(String::from("Usage: completions ")); return } - if let Err(e) = generate_completions(parts[1]) { - println!("Error while executing completions command: {e}") + match generate_completions(parts[1]) { + Ok(completions) => output.push(completions), + Err(e) => output.push(format!("Error while executing completions command: {e}")), } } /// Auxiliary function to define the wallet command handling. -async fn handle_wallet(drk: &DrkPtr, parts: &[&str]) { +async fn handle_wallet(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec) { // Check correct command structure if parts.len() < 2 { - println!("Malformed `wallet` command"); - println!("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)"); + output.push(String::from("Malformed `wallet` command")); + output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)")); return } // Handle subcommand match parts[1] { - "initialize" => handle_wallet_initialize(drk).await, - "keygen" => handle_wallet_keygen(drk).await, - "balance" => handle_wallet_balance(drk).await, - "address" => handle_wallet_address(drk).await, - "addresses" => handle_wallet_addresses(drk).await, - "default-address" => handle_wallet_default_address(drk, parts).await, - "secrets" => handle_wallet_secrets(drk).await, - "import-secrets" => handle_wallet_import_secrets(drk).await, - "tree" => handle_wallet_tree(drk).await, - "coins" => handle_wallet_coins(drk).await, + "initialize" => handle_wallet_initialize(drk, output).await, + "keygen" => handle_wallet_keygen(drk, output).await, + "balance" => handle_wallet_balance(drk, output).await, + "address" => handle_wallet_address(drk, output).await, + "addresses" => handle_wallet_addresses(drk, output).await, + "default-address" => handle_wallet_default_address(drk, parts, output).await, + "secrets" => handle_wallet_secrets(drk, output).await, + "import-secrets" => handle_wallet_import_secrets(drk, input, output).await, + "tree" => handle_wallet_tree(drk, output).await, + "coins" => handle_wallet_coins(drk, output).await, _ => { - println!("Unreconized wallet subcommand: {}", parts[1]); - println!("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)"); + output.push(format!("Unreconized wallet subcommand: {}", parts[1])); + output.push(String::from("Usage: wallet (initialize|keygen|balance|address|addresses|default-address|secrets|import-secrets|tree|coins)")); } } } /// Auxiliary function to define the wallet initialize subcommand handling. -async fn handle_wallet_initialize(drk: &DrkPtr) { +async fn handle_wallet_initialize(drk: &DrkPtr, output: &mut Vec) { let lock = drk.read().await; if let Err(e) = lock.initialize_wallet().await { - println!("Error initializing wallet: {e:?}"); + output.push(format!("Error initializing wallet: {e:?}")); return } if let Err(e) = lock.initialize_money().await { - println!("Failed to initialize Money: {e:?}"); + output.push(format!("Failed to initialize Money: {e:?}")); return } if let Err(e) = lock.initialize_dao().await { - println!("Failed to initialize DAO: {e:?}"); + output.push(format!("Failed to initialize DAO: {e:?}")); return } if let Err(e) = lock.initialize_deployooor() { - println!("Failed to initialize Deployooor: {e:?}"); + output.push(format!("Failed to initialize Deployooor: {e:?}")); } } /// Auxiliary function to define the wallet keygen subcommand handling. -async fn handle_wallet_keygen(drk: &DrkPtr) { - if let Err(e) = drk.read().await.money_keygen().await { - println!("Failed to generate keypair: {e:?}"); +async fn handle_wallet_keygen(drk: &DrkPtr, output: &mut Vec) { + if let Err(e) = drk.read().await.money_keygen(output).await { + output.push(format!("Failed to generate keypair: {e:?}")); } } /// Auxiliary function to define the wallet balance subcommand handling. -async fn handle_wallet_balance(drk: &DrkPtr) { +async fn handle_wallet_balance(drk: &DrkPtr, output: &mut Vec) { let lock = drk.read().await; let balmap = match lock.money_balance().await { Ok(m) => m, Err(e) => { - println!("Failed to fetch balances map: {e:?}"); + output.push(format!("Failed to fetch balances map: {e:?}")); return } }; @@ -550,7 +545,7 @@ async fn handle_wallet_balance(drk: &DrkPtr) { let aliases_map = match lock.get_aliases_mapped_by_token().await { Ok(m) => m, Err(e) => { - println!("Failed to fetch aliases map: {e:?}"); + output.push(format!("Failed to fetch aliases map: {e:?}")); return } }; @@ -569,26 +564,26 @@ async fn handle_wallet_balance(drk: &DrkPtr) { } if table.is_empty() { - println!("No unspent balances found"); + output.push(String::from("No unspent balances found")); } else { - println!("{table}"); + output.push(format!("{table}")); } } /// Auxiliary function to define the wallet address subcommand handling. -async fn handle_wallet_address(drk: &DrkPtr) { +async fn handle_wallet_address(drk: &DrkPtr, output: &mut Vec) { match drk.read().await.default_address().await { - Ok(address) => println!("{address}"), - Err(e) => println!("Failed to fetch default address: {e:?}"), + Ok(address) => output.push(format!("{address}")), + Err(e) => output.push(format!("Failed to fetch default address: {e:?}")), } } /// Auxiliary function to define the wallet addresses subcommand handling. -async fn handle_wallet_addresses(drk: &DrkPtr) { +async fn handle_wallet_addresses(drk: &DrkPtr, output: &mut Vec) { let addresses = match drk.read().await.addresses().await { Ok(a) => a, Err(e) => { - println!("Failed to fetch addresses: {e:?}"); + output.push(format!("Failed to fetch addresses: {e:?}")); return } }; @@ -606,90 +601,102 @@ async fn handle_wallet_addresses(drk: &DrkPtr) { } if table.is_empty() { - println!("No addresses found"); + output.push(String::from("No addresses found")); } else { - println!("{table}"); + output.push(format!("{table}")); } } /// Auxiliary function to define the wallet default address subcommand handling. -async fn handle_wallet_default_address(drk: &DrkPtr, parts: &[&str]) { +async fn handle_wallet_default_address(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { if parts.len() != 3 { - println!("Malformed `wallet default-address` subcommand"); - println!("Usage: wallet default-address "); + output.push(String::from("Malformed `wallet default-address` subcommand")); + output.push(String::from("Usage: wallet default-address ")); return } let index = match usize::from_str(parts[2]) { Ok(i) => i, Err(e) => { - println!("Invalid address id: {e:?}"); + output.push(format!("Invalid address id: {e:?}")); return } }; if let Err(e) = drk.read().await.set_default_address(index) { - println!("Failed to set default address: {e:?}"); + output.push(format!("Failed to set default address: {e:?}")); } } /// Auxiliary function to define the wallet secrets subcommand handling. -async fn handle_wallet_secrets(drk: &DrkPtr) { +async fn handle_wallet_secrets(drk: &DrkPtr, output: &mut Vec) { match drk.read().await.get_money_secrets().await { Ok(secrets) => { for secret in secrets { - println!("{secret}"); + output.push(format!("{secret}")); } } - Err(e) => println!("Failed to fetch secrets: {e:?}"), + Err(e) => output.push(format!("Failed to fetch secrets: {e:?}")), } } /// Auxiliary function to define the wallet import secrets subcommand handling. -async fn handle_wallet_import_secrets(drk: &DrkPtr) { +async fn handle_wallet_import_secrets(drk: &DrkPtr, input: &[String], output: &mut Vec) { let mut secrets = vec![]; - // TODO: read from a file here not stdin - let lines = stdin().lines(); - for (i, line) in lines.enumerate() { - if let Ok(line) = line { + // Parse input or read from stdin + if input.is_empty() { + for (i, line) in stdin().lines().enumerate() { + let Ok(line) = line else { continue }; + let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else { - println!("Warning: Failed to decode secret on line {i}"); + output.push(format!("Warning: Failed to decode secret on line {i}")); continue }; let Ok(secret) = deserialize_async(&bytes).await else { - println!("Warning: Failed to deserialize secret on line {i}"); + output.push(format!("Warning: Failed to deserialize secret on line {i}")); + continue + }; + secrets.push(secret); + } + } else { + for (i, line) in input.iter().enumerate() { + let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else { + output.push(format!("Warning: Failed to decode secret on line {i}")); + continue + }; + let Ok(secret) = deserialize_async(&bytes).await else { + output.push(format!("Warning: Failed to deserialize secret on line {i}")); continue }; secrets.push(secret); } } - match drk.read().await.import_money_secrets(secrets).await { + match drk.read().await.import_money_secrets(secrets, output).await { Ok(pubkeys) => { for key in pubkeys { - println!("{key}"); + output.push(format!("{key}")); } } - Err(e) => println!("Failed to import secrets: {e:?}"), + Err(e) => output.push(format!("Failed to import secrets: {e:?}")), } } /// Auxiliary function to define the wallet tree subcommand handling. -async fn handle_wallet_tree(drk: &DrkPtr) { - // TODO: write to a file here not stdout +async fn handle_wallet_tree(drk: &DrkPtr, output: &mut Vec) { match drk.read().await.get_money_tree().await { - Ok(tree) => println!("{tree:#?}"), - Err(e) => println!("Failed to fetch tree: {e:?}"), + Ok(tree) => output.push(format!("{tree:#?}")), + Err(e) => output.push(format!("Failed to fetch tree: {e:?}")), } } /// Auxiliary function to define the wallet coins subcommand handling. -async fn handle_wallet_coins(drk: &DrkPtr) { +async fn handle_wallet_coins(drk: &DrkPtr, output: &mut Vec) { let lock = drk.read().await; let coins = match lock.get_coins(true).await { Ok(c) => c, Err(e) => { - println!("Failed to fetch coins: {e:?}"); + output.push(format!("Failed to fetch coins: {e:?}")); return } }; @@ -701,7 +708,7 @@ async fn handle_wallet_coins(drk: &DrkPtr) { let aliases_map = match lock.get_aliases_mapped_by_token().await { Ok(m) => m, Err(e) => { - println!("Failed to fetch aliases map: {e:?}"); + output.push(format!("Failed to fetch aliases map: {e:?}")); return } }; @@ -761,37 +768,37 @@ async fn handle_wallet_coins(drk: &DrkPtr) { ]); } - println!("{table}"); + output.push(format!("{table}")); } /// Auxiliary function to define the spend command handling. -async fn handle_spend(drk: &DrkPtr) { - let tx = match parse_tx_from_stdin().await { +async fn handle_spend(drk: &DrkPtr, input: &[String], output: &mut Vec) { + let tx = match parse_tx_from_input(input).await { Ok(t) => t, Err(e) => { - println!("Error while parsing transaction: {e}"); + output.push(format!("Error while parsing transaction: {e}")); return } }; - if let Err(e) = drk.read().await.mark_tx_spend(&tx).await { - println!("Failed to mark transaction coins as spent: {e}") + if let Err(e) = drk.read().await.mark_tx_spend(&tx, output).await { + output.push(format!("Failed to mark transaction coins as spent: {e}")) } } /// Auxiliary function to define the unspend command handling. -async fn handle_unspend(drk: &DrkPtr, parts: &[&str]) { +async fn handle_unspend(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct command structure if parts.len() != 2 { - println!("Malformed `unspend` command"); - println!("Usage: unspend "); + output.push(String::from("Malformed `unspend` command")); + output.push(String::from("Usage: unspend ")); return } let bytes = match bs58::decode(&parts[1]).into_vec() { Ok(b) => b, Err(e) => { - println!("Invalid coin: {e}"); + output.push(format!("Invalid coin: {e}")); return } }; @@ -799,7 +806,7 @@ async fn handle_unspend(drk: &DrkPtr, parts: &[&str]) { let bytes: [u8; 32] = match bytes.try_into() { Ok(b) => b, Err(e) => { - println!("Invalid coin: {e:?}"); + output.push(format!("Invalid coin: {e:?}")); return } }; @@ -807,24 +814,24 @@ async fn handle_unspend(drk: &DrkPtr, parts: &[&str]) { let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() { Some(v) => v, None => { - println!("Invalid coin"); + output.push(String::from("Invalid coin")); return } }; if let Err(e) = drk.read().await.unspend_coin(&Coin::from(elem)).await { - println!("Failed to mark coin as unspent: {e}") + output.push(format!("Failed to mark coin as unspent: {e}")) } } /// Auxiliary function to define the transfer command handling. -async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { +async fn handle_transfer(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct command structure if parts.len() < 4 || parts.len() > 7 { - println!("Malformed `transfer` command"); - println!( - "Usage: transfer [--half-split] [spend_hook] [user_data]" - ); + output.push(String::from("Malformed `transfer` command")); + output.push(String::from( + "Usage: transfer [--half-split] [spend_hook] [user_data]", + )); return } @@ -838,7 +845,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { let amount = String::from(parts[index]); if let Err(e) = f64::from_str(&amount) { - println!("Invalid amount: {e}"); + output.push(format!("Invalid amount: {e}")); return } index += 1; @@ -847,7 +854,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { let token_id = match lock.get_token(String::from(parts[index])).await { Ok(t) => t, Err(e) => { - println!("Invalid token alias: {e}"); + output.push(format!("Invalid token alias: {e}")); return } }; @@ -856,7 +863,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { let rcpt = match PublicKey::from_str(parts[index]) { Ok(r) => r, Err(e) => { - println!("Invalid recipient: {e}"); + output.push(format!("Invalid recipient: {e}")); return } }; @@ -866,7 +873,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { match FuncId::from_str(parts[index]) { Ok(s) => Some(s), Err(e) => { - println!("Invalid spend hook: {e}"); + output.push(format!("Invalid spend hook: {e}")); return } } @@ -879,7 +886,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { let bytes = match bs58::decode(&parts[index]).into_vec() { Ok(b) => b, Err(e) => { - println!("Invalid user data: {e}"); + output.push(format!("Invalid user data: {e}")); return } }; @@ -887,7 +894,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { let bytes: [u8; 32] = match bytes.try_into() { Ok(b) => b, Err(e) => { - println!("Invalid user data: {e:?}"); + output.push(format!("Invalid user data: {e:?}")); return } }; @@ -895,7 +902,7 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { let elem: pallas::Base = match pallas::Base::from_repr(bytes).into() { Some(v) => v, None => { - println!("Invalid user data"); + output.push(String::from("Invalid user data")); return } }; @@ -905,48 +912,47 @@ async fn handle_transfer(drk: &DrkPtr, parts: &[&str]) { None }; - // TODO: write to a file here not stdout match lock.transfer(&amount, token_id, rcpt, spend_hook, user_data, half_split).await { - Ok(t) => println!("{}", base64::encode(&serialize_async(&t).await)), - Err(e) => println!("Failed to create payment transaction: {e}"), + Ok(t) => output.push(base64::encode(&serialize_async(&t).await)), + Err(e) => output.push(format!("Failed to create payment transaction: {e}")), } } /// Auxiliary function to define the otc command handling. -async fn handle_otc(drk: &DrkPtr, parts: &[&str]) { +async fn handle_otc(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec) { // Check correct command structure if parts.len() < 2 { - println!("Malformed `otc` command"); - println!("Usage: otc (init|join|inspect|sign)"); + output.push(String::from("Malformed `otc` command")); + output.push(String::from("Usage: otc (init|join|inspect|sign)")); return } // Handle subcommand match parts[1] { - "init" => handle_otc_init(drk, parts).await, - "join" => handle_otc_join(drk, parts).await, - "inspect" => handle_otc_inspect(drk, parts).await, - "sign" => handle_otc_sign(drk, parts).await, + "init" => handle_otc_init(drk, parts, output).await, + "join" => handle_otc_join(drk, parts, input, output).await, + "inspect" => handle_otc_inspect(drk, parts, input, output).await, + "sign" => handle_otc_sign(drk, parts, input, output).await, _ => { - println!("Unreconized OTC subcommand: {}", parts[1]); - println!("Usage: otc (init|join|inspect|sign)"); + output.push(format!("Unreconized OTC subcommand: {}", parts[1])); + output.push(String::from("Usage: otc (init|join|inspect|sign)")); } } } /// Auxiliary function to define the otc init subcommand handling. -async fn handle_otc_init(drk: &DrkPtr, parts: &[&str]) { +async fn handle_otc_init(drk: &DrkPtr, parts: &[&str], output: &mut Vec) { // Check correct subcommand structure if parts.len() != 4 { - println!("Malformed `otc init` subcommand"); - println!("Usage: otc init "); + output.push(String::from("Malformed `otc init` subcommand")); + output.push(String::from("Usage: otc init ")); return } let value_pair = match parse_value_pair(parts[2]) { Ok(v) => v, Err(e) => { - println!("Invalid value pair: {e}"); + output.push(format!("Invalid value pair: {e}")); return } }; @@ -955,153 +961,174 @@ async fn handle_otc_init(drk: &DrkPtr, parts: &[&str]) { let token_pair = match parse_token_pair(&lock, parts[3]).await { Ok(t) => t, Err(e) => { - println!("Invalid token pair: {e}"); + output.push(format!("Invalid token pair: {e}")); return } }; match lock.init_swap(value_pair, token_pair, None, None, None).await { - Ok(half) => println!("{}", base64::encode(&serialize_async(&half).await)), - Err(e) => eprintln!("Failed to create swap transaction half: {e}"), + Ok(half) => output.push(base64::encode(&serialize_async(&half).await)), + Err(e) => output.push(format!("Failed to create swap transaction half: {e}")), } } /// Auxiliary function to define the otc join subcommand handling. -async fn handle_otc_join(drk: &DrkPtr, parts: &[&str]) { +async fn handle_otc_join(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec) { // Check correct subcommand structure if parts.len() != 2 { - println!("Malformed `otc join` subcommand"); - println!("Usage: otc join"); + output.push(String::from("Malformed `otc join` subcommand")); + output.push(String::from("Usage: otc join")); return } - // TODO: read from a file here not stdin - let mut buf = String::new(); - if let Err(e) = stdin().read_to_string(&mut buf) { - println!("Failed to read from stdin: {e}"); - return + // Parse line from input or fallback to stdin if its empty + let buf = match input.len() { + 0 => { + let mut buf = String::new(); + if let Err(e) = stdin().read_to_string(&mut buf) { + output.push(format!("Failed to read from stdin: {e}")); + return + }; + buf + } + 1 => input[0].clone(), + _ => { + output.push(String::from("Multiline input provided")); + return + } }; let Some(bytes) = base64::decode(buf.trim()) else { - println!("Failed to decode partial swap data"); + output.push(String::from("Failed to decode partial swap data")); return }; let partial: PartialSwapData = match deserialize_async(&bytes).await { Ok(p) => p, Err(e) => { - println!("Failed to deserialize partial swap data: {e}"); + output.push(format!("Failed to deserialize partial swap data: {e}")); return } }; match drk.read().await.join_swap(partial, None, None, None).await { - Ok(tx) => println!("{}", base64::encode(&serialize_async(&tx).await)), - Err(e) => eprintln!("Failed to create a join swap transaction: {e}"), + Ok(tx) => output.push(base64::encode(&serialize_async(&tx).await)), + Err(e) => output.push(format!("Failed to create a join swap transaction: {e}")), } } /// Auxiliary function to define the otc inspect subcommand handling. -async fn handle_otc_inspect(drk: &DrkPtr, parts: &[&str]) { +async fn handle_otc_inspect( + drk: &DrkPtr, + parts: &[&str], + input: &[String], + output: &mut Vec, +) { // Check correct subcommand structure if parts.len() != 2 { - println!("Malformed `otc inspect` subcommand"); - println!("Usage: otc inspect"); + output.push(String::from("Malformed `otc inspect` subcommand")); + output.push(String::from("Usage: otc inspect")); return } - // TODO: read from a file here not stdin - let mut buf = String::new(); - if let Err(e) = stdin().read_to_string(&mut buf) { - println!("Failed to read from stdin: {e}"); - return + // Parse line from input or fallback to stdin if its empty + let buf = match input.len() { + 0 => { + let mut buf = String::new(); + if let Err(e) = stdin().read_to_string(&mut buf) { + output.push(format!("Failed to read from stdin: {e}")); + return + }; + buf + } + 1 => input[0].clone(), + _ => { + output.push(String::from("Multiline input provided")); + return + } }; let Some(bytes) = base64::decode(buf.trim()) else { - println!("Failed to decode swap transaction"); + output.push(String::from("Failed to decode swap transaction")); return }; - if let Err(e) = drk.read().await.inspect_swap(bytes).await { - println!("Failed to inspect swap: {e}"); + if let Err(e) = drk.read().await.inspect_swap(bytes, output).await { + output.push(format!("Failed to inspect swap: {e}")); } } /// Auxiliary function to define the otc sign subcommand handling. -async fn handle_otc_sign(drk: &DrkPtr, parts: &[&str]) { +async fn handle_otc_sign(drk: &DrkPtr, parts: &[&str], input: &[String], output: &mut Vec) { // Check correct subcommand structure if parts.len() != 2 { - println!("Malformed `otc sign` subcommand"); - println!("Usage: otc sign"); + output.push(String::from("Malformed `otc sign` subcommand")); + output.push(String::from("Usage: otc sign")); return } - // TODO: read from a file here not stdin - let mut tx = match parse_tx_from_stdin().await { + let mut tx = match parse_tx_from_input(input).await { Ok(t) => t, Err(e) => { - println!("Error while parsing transaction: {e}"); + output.push(format!("Error while parsing transaction: {e}")); return } }; match drk.read().await.sign_swap(&mut tx).await { - Ok(_) => println!("{}", base64::encode(&serialize_async(&tx).await)), - Err(e) => println!("Failed to sign joined swap transaction: {e}"), + Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)), + Err(e) => output.push(format!("Failed to sign joined swap transaction: {e}")), } } /// Auxiliary function to define the attach fee command handling. -async fn handle_attach_fee(drk: &DrkPtr) { - // TODO: read from a file here not stdin - let mut tx = match parse_tx_from_stdin().await { +async fn handle_attach_fee(drk: &DrkPtr, input: &[String], output: &mut Vec) { + let mut tx = match parse_tx_from_input(input).await { Ok(t) => t, Err(e) => { - println!("Error while parsing transaction: {e}"); + output.push(format!("Error while parsing transaction: {e}")); return } }; match drk.read().await.attach_fee(&mut tx).await { - Ok(_) => println!("{}", base64::encode(&serialize_async(&tx).await)), - Err(e) => println!("Failed to attach the fee call to the transaction: {e}"), + Ok(_) => output.push(base64::encode(&serialize_async(&tx).await)), + Err(e) => output.push(format!("Failed to attach the fee call to the transaction: {e}")), } } /// Auxiliary function to define the inspect command handling. -async fn handle_inspect() { - // TODO: read from a file here not stdin - match parse_tx_from_stdin().await { - Ok(tx) => println!("{tx:#?}"), - Err(e) => println!("Error while parsing transaction: {e}"), +async fn handle_inspect(input: &[String], output: &mut Vec) { + match parse_tx_from_input(input).await { + Ok(tx) => output.push(format!("{tx:#?}")), + Err(e) => output.push(format!("Error while parsing transaction: {e}")), } } /// Auxiliary function to define the broadcast command handling. -async fn handle_broadcast(drk: &DrkPtr) { - // TODO: read from a file here not stdin - let tx = match parse_tx_from_stdin().await { +async fn handle_broadcast(drk: &DrkPtr, input: &[String], output: &mut Vec) { + let tx = match parse_tx_from_input(input).await { Ok(t) => t, Err(e) => { - println!("Error while parsing transaction: {e}"); + output.push(format!("Error while parsing transaction: {e}")); return } }; let lock = drk.read().await; if let Err(e) = lock.simulate_tx(&tx).await { - println!("Failed to simulate tx: {e}"); + output.push(format!("Failed to simulate tx: {e}")); return }; - if let Err(e) = lock.mark_tx_spend(&tx).await { - println!("Failed to mark transaction coins as spent: {e}"); + if let Err(e) = lock.mark_tx_spend(&tx, output).await { + output.push(format!("Failed to mark transaction coins as spent: {e}")); return }; - match lock.broadcast_tx(&tx).await { - Ok(txid) => println!("Transaction ID: {txid}"), - Err(e) => println!("Failed to broadcast transaction: {e}"), + match lock.broadcast_tx(&tx, output).await { + Ok(txid) => output.push(format!("Transaction ID: {txid}")), + Err(e) => output.push(format!("Failed to broadcast transaction: {e}")), } } @@ -1113,18 +1140,13 @@ async fn handle_subscribe( subscription_task: &StoppableTaskPtr, shell_sender: &Sender>, ex: &ExecutorPtr, + output: &mut Vec, ) { if *subscription_active { - println!("Subscription is already active!"); + output.push(String::from("Subscription is already active!")); return } - if let Err(e) = drk.read().await.scan_blocks().await { - println!("Failed during scanning: {e:?}"); - return - } - println!("Finished scanning blockchain"); - // Start the subcristion task let drk_ = drk.clone(); let endpoint_ = endpoint.clone(); @@ -1146,9 +1168,13 @@ async fn handle_subscribe( } /// Auxiliary function to define the unsubscribe command handling. -async fn handle_unsubscribe(subscription_active: &mut bool, subscription_task: &StoppableTaskPtr) { +async fn handle_unsubscribe( + subscription_active: &mut bool, + subscription_task: &StoppableTaskPtr, + output: &mut Vec, +) { if !*subscription_active { - println!("Subscription is already inactive!"); + output.push(String::from("Subscription is already inactive!")); return } subscription_task.stop().await; @@ -1156,15 +1182,20 @@ async fn handle_unsubscribe(subscription_active: &mut bool, subscription_task: & } /// Auxiliary function to define the scan command handling. -async fn handle_scan(drk: &DrkPtr, subscription_active: &bool, parts: &[&str]) { +async fn handle_scan( + drk: &DrkPtr, + subscription_active: &bool, + parts: &[&str], + output: &mut Vec, +) { if *subscription_active { - println!("Subscription is already active!"); + output.push(String::from("Subscription is already active!")); return } // Check correct command structure if parts.len() != 1 && parts.len() != 3 { - println!("Malformed `scan` command"); + output.push(String::from("Malformed `scan` command")); return } @@ -1172,28 +1203,28 @@ async fn handle_scan(drk: &DrkPtr, subscription_active: &bool, parts: &[&str]) { let lock = drk.read().await; if parts.len() == 3 { if parts[1] != "--reset" { - println!("Malformed `scan` command"); - println!("Usage: scan --reset "); + output.push(String::from("Malformed `scan` command")); + output.push(String::from("Usage: scan --reset ")); return } let height = match u32::from_str(parts[2]) { Ok(h) => h, Err(e) => { - println!("Invalid reset height: {e:?}"); + output.push(format!("Invalid reset height: {e:?}")); return } }; - if let Err(e) = lock.reset_to_height(height) { - println!("Failed during wallet reset: {e:?}"); + if let Err(e) = lock.reset_to_height(height, output) { + output.push(format!("Failed during wallet reset: {e:?}")); return } } - if let Err(e) = lock.scan_blocks().await { - println!("Failed during scanning: {e:?}"); + if let Err(e) = lock.scan_blocks(output).await { + output.push(format!("Failed during scanning: {e:?}")); return } - println!("Finished scanning blockchain"); + output.push(String::from("Finished scanning blockchain")); } diff --git a/bin/drk/src/lib.rs b/bin/drk/src/lib.rs index 572a73dad..df60eb842 100644 --- a/bin/drk/src/lib.rs +++ b/bin/drk/src/lib.rs @@ -133,20 +133,20 @@ impl Drk { } /// Auxiliary function to completely reset wallet state. - pub fn reset(&self) -> WalletDbResult<()> { - println!("Resetting full wallet state"); - self.reset_scanned_blocks()?; - self.reset_money_tree()?; - self.reset_money_smt()?; - self.reset_money_coins()?; - self.reset_mint_authorities()?; - self.reset_dao_trees()?; - self.reset_daos()?; - self.reset_dao_proposals()?; - self.reset_dao_votes()?; - self.reset_deploy_authorities()?; - self.reset_tx_history()?; - println!("Successfully reset full wallet state"); + pub fn reset(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting full wallet state")); + self.reset_scanned_blocks(output)?; + self.reset_money_tree(output)?; + self.reset_money_smt(output)?; + self.reset_money_coins(output)?; + self.reset_mint_authorities(output)?; + self.reset_dao_trees(output)?; + self.reset_daos(output)?; + self.reset_dao_proposals(output)?; + self.reset_dao_votes(output)?; + self.reset_deploy_authorities(output)?; + self.reset_tx_history(output)?; + output.push(String::from("Successfully reset full wallet state")); Ok(()) } } diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index 509ff0f32..40100dd27 100644 --- a/bin/drk/src/main.rs +++ b/bin/drk/src/main.rs @@ -55,6 +55,7 @@ use darkfi_serial::{deserialize_async, serialize_async}; use drk::{ cli_util::{ generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair, + print_output, }, dao::{DaoParams, ProposalRecord}, interactive::interactive, @@ -687,11 +688,16 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { args.fun, ) .await; - drk.ping().await?; + let mut output = vec![]; + drk.ping(&mut output).await?; + print_output(&output); drk.stop_rpc_client().await } - Subcmd::Completions { shell } => generate_completions(&shell), + Subcmd::Completions { shell } => { + println!("{}", generate_completions(&shell)?); + Ok(()) + } Subcmd::Wallet { command } => { let drk = new_wallet( @@ -725,10 +731,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { } WalletSubcmd::Keygen => { - if let Err(e) = drk.money_keygen().await { + let mut output = vec![]; + if let Err(e) = drk.money_keygen(&mut output).await { eprintln!("Failed to generate keypair: {e:?}"); exit(2); } + print_output(&output); } WalletSubcmd::Balance => { @@ -817,8 +825,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { } } - let pubkeys = match drk.import_money_secrets(secrets).await { - Ok(p) => p, + let mut output = vec![]; + let pubkeys = match drk.import_money_secrets(secrets, &mut output).await { + Ok(p) => { + print_output(&output); + p + } Err(e) => { eprintln!("Failed to import secret keys into wallet: {e:?}"); exit(2); @@ -922,10 +934,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { ) .await; - if let Err(e) = drk.mark_tx_spend(&tx).await { + let mut output = vec![]; + if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await { eprintln!("Failed to mark transaction coins as spent: {e:?}"); exit(2); }; + print_output(&output); Ok(()) } @@ -1119,10 +1133,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { args.fun, ) .await; - if let Err(e) = drk.inspect_swap(bytes).await { + let mut output = vec![]; + if let Err(e) = drk.inspect_swap(bytes, &mut output).await { eprintln!("Failed to inspect swap: {e:?}"); exit(2); }; + print_output(&output); Ok(()) } @@ -1953,18 +1969,20 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { exit(2); }; - if let Err(e) = drk.mark_tx_spend(&tx).await { + let mut output = vec![]; + if let Err(e) = drk.mark_tx_spend(&tx, &mut output).await { eprintln!("Failed to mark transaction coins as spent: {e:?}"); exit(2); }; - let txid = match drk.broadcast_tx(&tx).await { + let txid = match drk.broadcast_tx(&tx, &mut output).await { Ok(t) => t, Err(e) => { eprintln!("Failed to broadcast transaction: {e:?}"); exit(2); } }; + print_output(&output); println!("Transaction ID: {txid}"); @@ -1982,17 +2000,19 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> { ) .await; + let mut output = vec![]; if let Some(height) = reset { - if let Err(e) = drk.reset_to_height(height) { + if let Err(e) = drk.reset_to_height(height, &mut output) { eprintln!("Failed during wallet reset: {e:?}"); exit(2); } } - if let Err(e) = drk.scan_blocks().await { + if let Err(e) = drk.scan_blocks(&mut output).await { eprintln!("Failed during scanning: {e:?}"); exit(2); } + print_output(&output); println!("Finished scanning blockchain"); drk.stop_rpc_client().await diff --git a/bin/drk/src/money.rs b/bin/drk/src/money.rs index 27acd37a8..d132e1577 100644 --- a/bin/drk/src/money.rs +++ b/bin/drk/src/money.rs @@ -132,8 +132,8 @@ impl Drk { } /// Generate a new keypair and place it into the wallet. - pub async fn money_keygen(&self) -> WalletDbResult<()> { - println!("Generating a new keypair"); + pub async fn money_keygen(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Generating a new keypair")); // TODO: We might want to have hierarchical deterministic key derivation. let keypair = Keypair::random(&mut OsRng); @@ -155,8 +155,8 @@ impl Drk { ], )?; - println!("New address:"); - println!("{}", keypair.public); + output.push(String::from("New address:")); + output.push(format!("{}", keypair.public)); Ok(()) } @@ -297,7 +297,11 @@ impl Drk { /// Import given secret keys into the wallet. /// If the key already exists, it will be skipped. /// Returns the respective PublicKey objects for the imported keys. - pub async fn import_money_secrets(&self, secrets: Vec) -> Result> { + pub async fn import_money_secrets( + &self, + secrets: Vec, + output: &mut Vec, + ) -> Result> { let existing_secrets = self.get_money_secrets().await?; let mut ret = Vec::with_capacity(secrets.len()); @@ -305,7 +309,7 @@ impl Drk { for secret in secrets { // Check if secret already exists if existing_secrets.contains(&secret) { - println!("Existing key found: {secret}"); + output.push(format!("Existing key found: {secret}")); continue } @@ -1014,7 +1018,7 @@ impl Drk { } /// Mark provided transaction input coins as spent. - pub async fn mark_tx_spend(&self, tx: &Transaction) -> Result<()> { + pub async fn mark_tx_spend(&self, tx: &Transaction, output: &mut Vec) -> Result<()> { // Create a cache of all our own nullifiers let mut owncoins_nullifiers = BTreeMap::new(); for coin in self.get_coins(true).await? { @@ -1025,13 +1029,13 @@ impl Drk { } let tx_hash = tx.hash().to_string(); - println!("[mark_tx_spend] Processing transaction: {tx_hash}"); + output.push(format!("[mark_tx_spend] Processing transaction: {tx_hash}")); for (i, call) in tx.calls.iter().enumerate() { if call.data.contract_id != *MONEY_CONTRACT_ID { continue } - println!("[mark_tx_spend] Found Money contract in call {i}"); + output.push(format!("[mark_tx_spend] Found Money contract in call {i}")); let nullifiers = self.money_call_nullifiers(call).await?; self.mark_spent_coins(None, &owncoins_nullifiers, &nullifiers, &None, &tx_hash)?; } @@ -1101,57 +1105,67 @@ impl Drk { } /// Reset the Money Merkle tree in the cache. - pub fn reset_money_tree(&self) -> WalletDbResult<()> { - println!("Resetting Money Merkle tree"); + pub fn reset_money_tree(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting Money Merkle tree")); if let Err(e) = self.cache.merkle_trees.remove(SLED_MERKLE_TREES_MONEY) { - println!("[reset_money_tree] Resetting Money Merkle tree failed: {e:?}"); + output.push(format!("[reset_money_tree] Resetting Money Merkle tree failed: {e:?}")); return Err(WalletDbError::GenericError) } - println!("Successfully reset Money Merkle tree"); + output.push(String::from("Successfully reset Money Merkle tree")); Ok(()) } /// Reset the Money nullifiers Sparse Merkle Tree in the cache. - pub fn reset_money_smt(&self) -> WalletDbResult<()> { - println!("Resetting Money Sparse Merkle tree"); + pub fn reset_money_smt(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting Money Sparse Merkle tree")); if let Err(e) = self.cache.money_smt.clear() { - println!("[reset_money_smt] Resetting Money Sparse Merkle tree failed: {e:?}"); + output.push(format!( + "[reset_money_smt] Resetting Money Sparse Merkle tree failed: {e:?}" + )); return Err(WalletDbError::GenericError) } - println!("Successfully reset Money Sparse Merkle tree"); + output.push(String::from("Successfully reset Money Sparse Merkle tree")); Ok(()) } /// Reset the Money coins in the wallet. - pub fn reset_money_coins(&self) -> WalletDbResult<()> { - println!("Resetting coins"); + pub fn reset_money_coins(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting coins")); let query = format!("DELETE FROM {};", *MONEY_COINS_TABLE); self.wallet.exec_sql(&query, &[])?; - println!("Successfully reset coins"); + output.push(String::from("Successfully reset coins")); Ok(()) } /// Remove the Money coins in the wallet that were created after /// provided height. - pub fn remove_money_coins_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Removing coins after: {height}"); + pub fn remove_money_coins_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Removing coins after: {height}")); let query = format!( "DELETE FROM {} WHERE {} > ?1;", *MONEY_COINS_TABLE, MONEY_COINS_COL_CREATION_HEIGHT ); self.wallet.exec_sql(&query, rusqlite::params![height])?; - println!("Successfully removed coins"); + output.push(String::from("Successfully removed coins")); Ok(()) } /// Mark the Money coins in the wallet that were spent after /// provided height as unspent. - pub fn unspent_money_coins_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Unspenting coins after: {height}"); + pub fn unspent_money_coins_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Unspenting coins after: {height}")); let query = format!( "UPDATE {} SET {} = 0, {} = NULL, {} = '=' WHERE {} > ?1;", *MONEY_COINS_TABLE, @@ -1161,7 +1175,7 @@ impl Drk { MONEY_COINS_COL_SPENT_HEIGHT ); self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; - println!("Successfully unspent coins"); + output.push(String::from("Successfully unspent coins")); Ok(()) } diff --git a/bin/drk/src/rpc.rs b/bin/drk/src/rpc.rs index 5086aec83..38a9f046d 100644 --- a/bin/drk/src/rpc.rs +++ b/bin/drk/src/rpc.rs @@ -284,7 +284,7 @@ impl Drk { /// Scans the blockchain for wallet relevant transactions, /// starting from the last scanned block. If a reorg has happened, /// we revert to its previous height and then scan from there. - pub async fn scan_blocks(&self) -> WalletDbResult<()> { + pub async fn scan_blocks(&self, output: &mut Vec) -> WalletDbResult<()> { // Grab last scanned block height let (mut height, hash) = self.get_last_scanned_block()?; @@ -294,7 +294,7 @@ impl Drk { // Check if block was found Err(Error::JsonRpcError((-32121, _))) => None, Err(e) => { - eprintln!("[scan_blocks] RPC client request failed: {e:?}"); + output.push(format!("[scan_blocks] RPC client request failed: {e:?}")); return Err(WalletDbError::GenericError) } }; @@ -302,7 +302,7 @@ impl Drk { // Check if a reorg has happened if block.is_none() || hash != block.unwrap().hash().to_string() { // Find the exact block height the reorg happened - println!("A reorg has happened, finding last known common block..."); + output.push(String::from("A reorg has happened, finding last known common block...")); height = height.saturating_sub(1); while height != 0 { // Grab our scanned block hash for that height @@ -314,7 +314,7 @@ impl Drk { // Check if block was found Err(Error::JsonRpcError((-32121, _))) => None, Err(e) => { - eprintln!("[scan_blocks] RPC client request failed: {e:?}"); + output.push(format!("[scan_blocks] RPC client request failed: {e:?}")); return Err(WalletDbError::GenericError) } }; @@ -326,8 +326,8 @@ impl Drk { } // Reset to its height - println!("Last common block found: {height} - {scanned_block_hash}"); - self.reset_to_height(height)?; + output.push(format!("Last common block found: {height} - {scanned_block_hash}")); + self.reset_to_height(height, output)?; break } } @@ -335,7 +335,7 @@ impl Drk { // If last scanned block is genesis(0) we reset, // otherwise continue with the next block height. if height == 0 { - self.reset()?; + self.reset(output)?; } else { height += 1; } @@ -344,22 +344,24 @@ impl Drk { let mut scan_cache = match self.scan_cache().await { Ok(c) => c, Err(e) => { - eprintln!("[scan_blocks] Generating scan cache failed: {e:?}"); + output.push(format!("[scan_blocks] Generating scan cache failed: {e:?}")); return Err(WalletDbError::GenericError) } }; loop { // Grab last confirmed block - println!("Requested to scan from block number: {height}"); + output.push(format!("Requested to scan from block number: {height}")); let (last_height, last_hash) = match self.get_last_confirmed_block().await { Ok(last) => last, Err(e) => { - eprintln!("[scan_blocks] RPC client request failed: {e:?}"); + output.push(format!("[scan_blocks] RPC client request failed: {e:?}")); return Err(WalletDbError::GenericError) } }; - println!("Last confirmed block reported by darkfid: {last_height} - {last_hash}"); + output.push(format!( + "Last confirmed block reported by darkfid: {last_height} - {last_hash}" + )); // Already scanned last confirmed block if height > last_height { @@ -367,21 +369,21 @@ impl Drk { } while height <= last_height { - println!("Requesting block {height}..."); + output.push(format!("Requesting block {height}...")); let block = match self.get_block_by_height(height).await { Ok(b) => b, Err(e) => { - eprintln!("[scan_blocks] RPC client request failed: {e:?}"); + output.push(format!("[scan_blocks] RPC client request failed: {e:?}")); return Err(WalletDbError::GenericError) } }; - println!("Block {height} received! Scanning block..."); + output.push(format!("Block {height} received! Scanning block...")); if let Err(e) = self.scan_block(&mut scan_cache, &block).await { - eprintln!("[scan_blocks] Scan block failed: {e:?}"); + output.push(format!("[scan_blocks] Scan block failed: {e:?}")); return Err(WalletDbError::GenericError) }; for msg in scan_cache.flush_messages() { - println!("{msg}"); + output.push(msg); } height += 1; } @@ -416,8 +418,8 @@ impl Drk { /// Broadcast a given transaction to darkfid and forward onto the network. /// Returns the transaction ID upon success. - pub async fn broadcast_tx(&self, tx: &Transaction) -> Result { - println!("Broadcasting transaction..."); + pub async fn broadcast_tx(&self, tx: &Transaction, output: &mut Vec) -> Result { + output.push(String::from("Broadcasting transaction...")); let params = JsonValue::Array(vec![JsonValue::String(base64::encode(&serialize_async(tx).await))]); @@ -524,13 +526,13 @@ impl Drk { } /// Auxiliary function to ping configured darkfid daemon for liveness. - pub async fn ping(&self) -> Result<()> { - println!("Executing ping request to darkfid..."); + pub async fn ping(&self, output: &mut Vec) -> Result<()> { + output.push(String::from("Executing ping request to darkfid...")); let latency = Instant::now(); let rep = self.darkfid_daemon_request("ping", &JsonValue::Array(vec![])).await?; let latency = latency.elapsed(); - println!("Got reply: {rep:?}"); - println!("Latency: {latency:?}"); + output.push(format!("Got reply: {rep:?}")); + output.push(format!("Latency: {latency:?}")); Ok(()) } @@ -569,17 +571,31 @@ pub async fn subscribe_blocks( endpoint: Url, ex: &ExecutorPtr, ) -> Result<()> { - // Grab last confirmed block height + // First we do a clean scan let lock = drk.read().await; + let mut output = vec![]; + if let Err(e) = lock.scan_blocks(&mut output).await { + let err_msg = format!("Failed during scanning: {e:?}"); + output.push(err_msg.clone()); + shell_sender.send(output).await?; + return Err(Error::Custom(err_msg)) + } + output.push(String::from("Finished scanning blockchain")); + shell_sender.send(output).await?; + + // Grab last confirmed block height let (last_confirmed_height, _) = lock.get_last_confirmed_block().await?; // Handle genesis(0) block if last_confirmed_height == 0 { - if let Err(e) = lock.scan_blocks().await { - return Err(Error::DatabaseError(format!( - "[subscribe_blocks] Scanning from genesis block failed: {e:?}" - ))) + output = vec![]; + if let Err(e) = lock.scan_blocks(&mut output).await { + let err_msg = format!("[subscribe_blocks] Scanning from genesis block failed: {e:?}"); + output.push(err_msg.clone()); + shell_sender.send(output).await?; + return Err(Error::Custom(err_msg)) } + shell_sender.send(output).await?; } // Grab last confirmed block again @@ -589,23 +605,23 @@ pub async fn subscribe_blocks( let (mut last_scanned_height, last_scanned_hash) = match lock.get_last_scanned_block() { Ok(last) => last, Err(e) => { - return Err(Error::DatabaseError(format!( - "[subscribe_blocks] Retrieving last scanned block failed: {e:?}" - ))) + let err_msg = format!("[subscribe_blocks] Retrieving last scanned block failed: {e:?}"); + shell_sender.send(vec![err_msg.clone()]).await?; + return Err(Error::Custom(err_msg)) } }; // Check if other blocks have been created if last_confirmed_height != last_scanned_height || last_confirmed_hash != last_scanned_hash { + let err_msg = String::from("[subscribe_blocks] Blockchain not fully scanned"); shell_sender .send(vec![ String::from("Warning: Last scanned block is not the last confirmed block."), String::from("You should first fully scan the blockchain, and then subscribe"), + err_msg.clone(), ]) .await?; - return Err(Error::DatabaseError( - "[subscribe_blocks] Blockchain not fully scanned".to_string(), - )) + return Err(Error::Custom(err_msg)) } let mut shell_message = @@ -644,12 +660,13 @@ pub async fn subscribe_blocks( shell_sender.send(shell_message).await?; drop(lock); - let e = loop { + let e = 'outer: loop { match subscription.receive().await { JsonResult::Notification(n) => { let mut shell_message = vec![String::from("Got Block notification from darkfid subscription")]; if n.method != "blockchain.subscribe_blocks" { + shell_sender.send(shell_message).await?; break Error::UnexpectedJsonRpc(format!( "Got foreign notification from darkfid: {}", n.method @@ -658,12 +675,14 @@ pub async fn subscribe_blocks( // Verify parameters if !n.params.is_array() { + shell_sender.send(shell_message).await?; break Error::UnexpectedJsonRpc( "Received notification params are not an array".to_string(), ) } let params = n.params.get::>().unwrap(); if params.is_empty() { + shell_sender.send(shell_message).await?; break Error::UnexpectedJsonRpc("Notification parameters are empty".to_string()) } @@ -679,10 +698,11 @@ pub async fn subscribe_blocks( let lock = drk.read().await; if block.header.height <= last_scanned_height { let reset_height = block.header.height.saturating_sub(1); - if let Err(e) = lock.reset_to_height(reset_height) { - return Err(Error::DatabaseError(format!( + if let Err(e) = lock.reset_to_height(reset_height, &mut shell_message) { + shell_sender.send(shell_message).await?; + break 'outer Error::Custom(format!( "[subscribe_blocks] Wallet state reset failed: {e:?}" - ))) + )) } // Scan genesis again if needed @@ -690,16 +710,18 @@ pub async fn subscribe_blocks( let genesis = match lock.get_block_by_height(reset_height).await { Ok(b) => b, Err(e) => { - return Err(Error::Custom(format!( + shell_sender.send(shell_message).await?; + break 'outer Error::Custom(format!( "[subscribe_blocks] RPC client request failed: {e:?}" - ))) + )) } }; let mut scan_cache = lock.scan_cache().await?; if let Err(e) = lock.scan_block(&mut scan_cache, &genesis).await { - return Err(Error::DatabaseError(format!( + shell_sender.send(shell_message).await?; + break 'outer Error::Custom(format!( "[subscribe_blocks] Scanning block failed: {e:?}" - ))) + )) }; for msg in scan_cache.flush_messages() { shell_message.push(msg); @@ -709,9 +731,10 @@ pub async fn subscribe_blocks( let mut scan_cache = lock.scan_cache().await?; if let Err(e) = lock.scan_block(&mut scan_cache, &block).await { - return Err(Error::DatabaseError(format!( + shell_sender.send(shell_message).await?; + break 'outer Error::Custom(format!( "[subscribe_blocks] Scanning block failed: {e:?}" - ))) + )) } for msg in scan_cache.flush_messages() { shell_message.push(msg); @@ -735,5 +758,6 @@ pub async fn subscribe_blocks( } }; + shell_sender.send(vec![format!("[subscribe_blocks] Subscription loop break: {e}")]).await?; Err(e) } diff --git a/bin/drk/src/scanned_blocks.rs b/bin/drk/src/scanned_blocks.rs index b3e4e589f..383b118e1 100644 --- a/bin/drk/src/scanned_blocks.rs +++ b/bin/drk/src/scanned_blocks.rs @@ -78,30 +78,34 @@ impl Drk { } /// Reset the scanned blocks information records in the cache. - pub fn reset_scanned_blocks(&self) -> WalletDbResult<()> { - println!("Resetting scanned blocks"); + pub fn reset_scanned_blocks(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting scanned blocks")); if let Err(e) = self.cache.scanned_blocks.clear() { - println!("[reset_scanned_blocks] Resetting scanned blocks tree failed: {e:?}"); + output.push(format!( + "[reset_scanned_blocks] Resetting scanned blocks tree failed: {e:?}" + )); return Err(WalletDbError::GenericError) } if let Err(e) = self.cache.state_inverse_diff.clear() { - println!("[reset_scanned_blocks] Resetting state inverse diffs tree failed: {e:?}"); + output.push(format!( + "[reset_scanned_blocks] Resetting state inverse diffs tree failed: {e:?}" + )); return Err(WalletDbError::GenericError) } - println!("Successfully reset scanned blocks"); + output.push(String::from("Successfully reset scanned blocks")); Ok(()) } /// Reset state to provided block height. /// If genesis block height(0) was provided, perform a full reset. - pub fn reset_to_height(&self, height: u32) -> WalletDbResult<()> { - println!("Resetting wallet state to block: {height}"); + pub fn reset_to_height(&self, height: u32, output: &mut Vec) -> WalletDbResult<()> { + output.push(format!("Resetting wallet state to block: {height}")); // If genesis block height(0) was provided, // perform a full reset. if height == 0 { - return self.reset() + return self.reset(output) } // Grab last scanned block height @@ -109,7 +113,9 @@ impl Drk { // Check if requested height is after it if last <= height { - println!("Requested block height is greater or equal to last scanned block"); + output.push(String::from( + "Requested block height is greater or equal to last scanned block", + )); return Ok(()) } @@ -117,7 +123,7 @@ impl Drk { let mut overlay = match CacheOverlay::new(&self.cache) { Ok(o) => o, Err(e) => { - println!("[reset_to_height] Creating cache overlay failed: {e:?}"); + output.push(format!("[reset_to_height] Creating cache overlay failed: {e:?}")); return Err(WalletDbError::GenericError) } }; @@ -128,70 +134,71 @@ impl Drk { let inverse_diff = match self.cache.get_state_inverse_diff(&height) { Ok(d) => d, Err(e) => { - println!( + output.push(format!( "[reset_to_height] Retrieving state inverse diff from cache failed: {e:?}" - ); + )); return Err(WalletDbError::GenericError) } }; // Apply it if let Err(e) = overlay.0.add_diff(&inverse_diff) { - println!("[reset_to_height] Adding state inverse diff to the cache overlay failed: {e:?}"); + output.push(format!("[reset_to_height] Adding state inverse diff to the cache overlay failed: {e:?}")); return Err(WalletDbError::GenericError) } if let Err(e) = overlay.0.apply_diff(&inverse_diff) { - println!("[reset_to_height] Applying state inverse diff to the cache overlay failed: {e:?}"); + output.push(format!("[reset_to_height] Applying state inverse diff to the cache overlay failed: {e:?}")); return Err(WalletDbError::GenericError) } // Remove it if let Err(e) = self.cache.state_inverse_diff.remove(height.to_be_bytes()) { - println!( + output.push(format!( "[reset_to_height] Removing state inverse diff from the cache failed: {e:?}" - ); + )); return Err(WalletDbError::GenericError) } // Flush sled if let Err(e) = self.cache.sled_db.flush() { - println!("[reset_to_height] Flushing cache sled database failed: {e:?}"); + output + .push(format!("[reset_to_height] Flushing cache sled database failed: {e:?}")); return Err(WalletDbError::GenericError) } } // Remove all wallet coins created after the reset height - self.remove_money_coins_after(&height)?; + self.remove_money_coins_after(&height, output)?; // Unspent all wallet coins spent after the reset height - self.unspent_money_coins_after(&height)?; + self.unspent_money_coins_after(&height, output)?; // Unfreeze tokens mint authorities frozen after the reset // height. - self.unfreeze_mint_authorities_after(&height)?; + self.unfreeze_mint_authorities_after(&height, output)?; // Unconfirm DAOs minted after the reset height - self.unconfirm_daos_after(&height)?; + self.unconfirm_daos_after(&height, output)?; // Unconfirm DAOs proposals minted after the reset height - self.unconfirm_dao_proposals_after(&height)?; + self.unconfirm_dao_proposals_after(&height, output)?; // Reset execution information for DAOs proposals executed // after the reset height. - self.unexec_dao_proposals_after(&height)?; + self.unexec_dao_proposals_after(&height, output)?; // Remove all DAOs proposals votes created after the reset // height. - self.remove_dao_votes_after(&height)?; + self.remove_dao_votes_after(&height, output)?; // Unfreeze all contracts frozen after the reset height - self.unfreeze_deploy_authorities_after(&height)?; + self.unfreeze_deploy_authorities_after(&height, output)?; // Set reverted status to all transactions executed after reset // height. - self.revert_transactions_after(&height)?; + self.revert_transactions_after(&height, output)?; - println!("Successfully reset wallet state"); + output.push(String::from("Successfully reset wallet state")); Ok(()) } } diff --git a/bin/drk/src/swap.rs b/bin/drk/src/swap.rs index 131848c33..c2f7be9bc 100644 --- a/bin/drk/src/swap.rs +++ b/bin/drk/src/swap.rs @@ -287,11 +287,11 @@ impl Drk { } /// Inspect and verify a given swap (half or full) transaction - pub async fn inspect_swap(&self, bytes: Vec) -> Result<()> { + pub async fn inspect_swap(&self, bytes: Vec, output: &mut Vec) -> Result<()> { // First we check if its a partial swap if let Ok(partial) = deserialize_async::(&bytes).await { // Inspect the PartialSwapData - println!("{partial}"); + output.push(format!("{partial}")); return Ok(()) } @@ -307,23 +307,23 @@ impl Drk { // We're inspecting a full transaction if tx.calls.len() != 1 { - eprintln!( + output.push(format!( "Found {} contract calls in the transaction, there should be 1", tx.calls.len() - ); + )); return insection_error } let params: MoneyTransferParamsV1 = deserialize_async(&tx.calls[0].data.data[1..]).await?; - println!("Parameters:\n{params:#?}"); + output.push(format!("Parameters:\n{params:#?}")); if params.inputs.len() != 2 { - eprintln!("Found {} inputs, there should be 2", params.inputs.len()); + output.push(format!("Found {} inputs, there should be 2", params.inputs.len())); return insection_error } if params.outputs.len() != 2 { - eprintln!("Found {} outputs, there should be 2", params.outputs.len()); + output.push(format!("Found {} outputs, there should be 2", params.outputs.len())); return insection_error } @@ -331,17 +331,18 @@ impl Drk { let secret_keys = self.get_money_secrets().await?; let mut skey: Option = None; let mut note: Option = None; - let mut output_idx = 0; + let mut param_output_idx = 0; - for output in ¶ms.outputs { - println!("Trying to decrypt note in output {output_idx}"); + for param_output in ¶ms.outputs { + output.push(format!("Trying to decrypt note in output {param_output_idx}")); for secret in &secret_keys { - if let Ok(d_note) = output.note.decrypt::(secret) { + if let Ok(d_note) = param_output.note.decrypt::(secret) { let s: SecretKey = deserialize_async(&d_note.memo).await?; skey = Some(s); note = Some(d_note); - println!("Successfully decrypted and found an ephemeral secret"); + output + .push(String::from("Successfully decrypted and found an ephemeral secret")); break } } @@ -350,20 +351,20 @@ impl Drk { break } - output_idx += 1; + param_output_idx += 1; } let Some(note) = note else { - eprintln!("Error: Could not decrypt notes of either output"); + output.push(String::from("Error: Could not decrypt notes of either output")); return insection_error }; - println!( - "Output[{output_idx}] value: {} ({})", + output.push(format!( + "Output[{param_output_idx}] value: {} ({})", note.value, encode_base10(note.value, BALANCE_BASE10_DECIMALS) - ); - println!("Output[{output_idx}] token ID: {}", note.token_id); + )); + output.push(format!("Output[{param_output_idx}] token ID: {}", note.token_id)); let skey = skey.unwrap(); let (pub_x, pub_y) = PublicKey::from_secret(skey).xy(); @@ -375,35 +376,43 @@ impl Drk { note.coin_blind.inner(), ])); - if coin == params.outputs[output_idx].coin { - println!("Output[{output_idx}] coin matches decrypted note metadata"); + if coin == params.outputs[param_output_idx].coin { + output.push(format!("Output[{param_output_idx}] coin matches decrypted note metadata")); } else { - eprintln!("Error: Output[{output_idx}] coin does not match note metadata"); + output.push(format!( + "Error: Output[{param_output_idx}] coin does not match note metadata" + )); return insection_error } let valcom = pedersen_commitment_u64(note.value, note.value_blind); let tokcom = poseidon_hash([note.token_id.inner(), note.token_blind.inner()]); - if valcom != params.outputs[output_idx].value_commit { - eprintln!("Error: Output[{output_idx}] value commitment does not match note metadata"); + if valcom != params.outputs[param_output_idx].value_commit { + output.push(format!( + "Error: Output[{param_output_idx}] value commitment does not match note metadata" + )); return insection_error } - if tokcom != params.outputs[output_idx].token_commit { - eprintln!("Error: Output[{output_idx}] token commitment does not match note metadata"); + if tokcom != params.outputs[param_output_idx].token_commit { + output.push(format!( + "Error: Output[{param_output_idx}] token commitment does not match note metadata" + )); return insection_error } - println!("Value and token commitments match decrypted note metadata"); + output.push(String::from("Value and token commitments match decrypted note metadata")); - // Verify that the output commitments match the other input commitments - match output_idx { + // Verify that the param output commitments match the other input commitments + match param_output_idx { 0 => { if valcom != params.inputs[1].value_commit || tokcom != params.inputs[1].token_commit { - eprintln!("Error: Value/Token commits of output[0] do not match input[1]"); + output.push(String::from( + "Error: Value/Token commits of output[0] do not match input[1]", + )); return insection_error } } @@ -411,14 +420,16 @@ impl Drk { if valcom != params.inputs[0].value_commit || tokcom != params.inputs[0].token_commit { - eprintln!("Error: Value/Token commits of output[1] do not match input[0]"); + output.push(String::from( + "Error: Value/Token commits of output[1] do not match input[0]", + )); return insection_error } } _ => unreachable!(), } - println!("Found matching pedersen commitments for outputs and inputs"); + output.push(String::from("Found matching pedersen commitments for outputs and inputs")); Ok(()) } @@ -475,7 +486,6 @@ impl Drk { } if !found { - eprintln!("Error: Failed to decrypt note with any of our secret keys"); return Err(Error::Custom( "Failed to decrypt note with any of our secret keys".to_string(), )) diff --git a/bin/drk/src/token.rs b/bin/drk/src/token.rs index 6c4c6510a..5745bb6dd 100644 --- a/bin/drk/src/token.rs +++ b/bin/drk/src/token.rs @@ -175,22 +175,26 @@ impl Drk { } /// Reset all token mint authorities frozen status in the wallet. - pub fn reset_mint_authorities(&self) -> WalletDbResult<()> { - println!("Resetting mint authorities frozen status"); + pub fn reset_mint_authorities(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting mint authorities frozen status")); let query = format!( "UPDATE {} SET {} = 0, {} = NULL;", *MONEY_TOKENS_TABLE, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_FREEZE_HEIGHT ); self.wallet.exec_sql(&query, &[])?; - println!("Successfully reset mint authorities frozen status"); + output.push(String::from("Successfully reset mint authorities frozen status")); Ok(()) } /// Remove token mint authorities frozen status in the wallet that /// where frozen after provided height. - pub fn unfreeze_mint_authorities_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Resetting mint authorities frozen status after: {height}"); + pub fn unfreeze_mint_authorities_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Resetting mint authorities frozen status after: {height}")); let query = format!( "UPDATE {} SET {} = 0, {} = NULL WHERE {} > ?1;", *MONEY_TOKENS_TABLE, @@ -199,7 +203,7 @@ impl Drk { MONEY_TOKENS_COL_FREEZE_HEIGHT ); self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; - println!("Successfully reset mint authorities frozen status"); + output.push(String::from("Successfully reset mint authorities frozen status")); Ok(()) } diff --git a/bin/drk/src/txs_history.rs b/bin/drk/src/txs_history.rs index 46e0f19ae..4f90b7f5c 100644 --- a/bin/drk/src/txs_history.rs +++ b/bin/drk/src/txs_history.rs @@ -172,19 +172,23 @@ impl Drk { } /// Reset the transaction history records in the wallet. - pub fn reset_tx_history(&self) -> WalletDbResult<()> { - println!("Resetting transactions history"); + pub fn reset_tx_history(&self, output: &mut Vec) -> WalletDbResult<()> { + output.push(String::from("Resetting transactions history")); let query = format!("DELETE FROM {WALLET_TXS_HISTORY_TABLE};"); self.wallet.exec_sql(&query, &[])?; - println!("Successfully reset transactions history"); + output.push(String::from("Successfully reset transactions history")); Ok(()) } /// Set reverted status to the transaction history records in the /// wallet that where executed after provided height. - pub fn revert_transactions_after(&self, height: &u32) -> WalletDbResult<()> { - println!("Reverting transactions history after: {height}"); + pub fn revert_transactions_after( + &self, + height: &u32, + output: &mut Vec, + ) -> WalletDbResult<()> { + output.push(format!("Reverting transactions history after: {height}")); let query = format!( "UPDATE {} SET {} = 'Reverted', {} = NULL WHERE {} > ?1;", WALLET_TXS_HISTORY_TABLE, @@ -193,7 +197,7 @@ impl Drk { WALLET_TXS_HISTORY_BLOCK_HEIGHT ); self.wallet.exec_sql(&query, rusqlite::params![Some(*height)])?; - println!("Successfully reverted transactions history"); + output.push(String::from("Successfully reverted transactions history")); Ok(()) }