Kaynağa Gözat

drk/interactive: wallet command handling added

skoupidi 1 yıl önce
ebeveyn
işleme
668220d47f
2 değiştirilmiş dosya ile 314 ekleme ve 5 silme
  1. 312 3
      bin/drk/src/interactive.rs
  2. 2 2
      bin/drk/src/main.rs

+ 312 - 3
bin/drk/src/interactive.rs

@@ -16,7 +16,10 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
-use std::{io::ErrorKind, str::FromStr};
+use std::{
+    io::{stdin, ErrorKind},
+    str::FromStr,
+};
 
 use futures::{select, FutureExt};
 use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK};
@@ -24,18 +27,23 @@ use linenoise_rs::{
     linenoise_history_add, linenoise_history_load, linenoise_history_save,
     linenoise_set_completion_callback, linenoise_set_hints_callback, LinenoiseState,
 };
+use prettytable::{format, row, Table};
 use smol::channel::{unbounded, Receiver, Sender};
 use url::Url;
 
 use darkfi::{
     cli_desc,
     system::{msleep, ExecutorPtr, StoppableTask, StoppableTaskPtr},
-    util::path::expand_path,
+    util::{parse::encode_base10, path::expand_path},
+    zk::halo2::Field,
     Error,
 };
+use darkfi_sdk::{crypto::FuncId, pasta::pallas};
+use darkfi_serial::{deserialize_async, serialize_async};
 
 use crate::{
     cli_util::{generate_completions, kaching},
+    money::BALANCE_BASE10_DECIMALS,
     rpc::subscribe_blocks,
     DrkPtr,
 };
@@ -54,6 +62,7 @@ fn help() {
     println!("\tkaching: Fun");
     println!("\tping: Send a ping request to the darkfid RPC endpoint");
     println!("\tcompletions: Generate a SHELL completion script and print to stdout");
+    println!("\twallet: Wallet operations");
     println!(
         "\tsubscribe: Perform a scan and then subscribe to darkfid to listen for incoming blocks"
     );
@@ -86,6 +95,21 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
         return
     }
 
+    if buf.starts_with("w") {
+        lc.push("wallet".to_string());
+        lc.push("wallet --initialize".to_string());
+        lc.push("wallet --keygen".to_string());
+        lc.push("wallet --balance".to_string());
+        lc.push("wallet --address".to_string());
+        lc.push("wallet --addresses".to_string());
+        lc.push("wallet --default_address".to_string());
+        lc.push("wallet --secrets".to_string());
+        lc.push("wallet --import_secrets".to_string());
+        lc.push("wallet --tree".to_string());
+        lc.push("wallet --coins".to_string());
+        return
+    }
+
     if buf.starts_with("su") {
         lc.push("subscribe".to_string());
         return
@@ -108,6 +132,7 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
 
     if buf.starts_with("sc") {
         lc.push("scan".to_string());
+        lc.push("scan --reset".to_string());
         return
     }
 
@@ -116,6 +141,7 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
         lc.push("subscribe".to_string());
         lc.push("snooze".to_string());
         lc.push("scan".to_string());
+        lc.push("scan --reset".to_string());
         return
     }
 
@@ -129,7 +155,11 @@ fn completion(buf: &str, lc: &mut Vec<String>) {
 fn hints(buf: &str) -> Option<(String, i32, bool)> {
     match buf {
         "completions " => Some(("{shell}".to_string(), 35, false)), // 35 = magenta
-        "scan " => Some(("--reset {height}".to_string(), 35, false)), // 35 = magenta
+        "wallet " => Some(("--{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}".to_string(), 35, false)),
+        "wallet -" => Some(("-{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}".to_string(), 35, false)),
+        "wallet --" => Some(("{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}".to_string(), 35, false)),
+        "wallet --default_address " => Some(("{address_id}".to_string(), 35, false)),
+        "scan --reset " => Some(("{height}".to_string(), 35, false)),
         _ => None,
     }
 }
@@ -196,6 +226,7 @@ pub async fn interactive(drk: &DrkPtr, endpoint: &Url, history_path: &str, ex: &
             "kaching" => kaching().await,
             "ping" => handle_ping(drk).await,
             "completions" => handle_completions(&parts),
+            "wallet" => handle_wallet(drk, &parts).await,
             "subscribe" => {
                 handle_subscribe(
                     drk,
@@ -333,6 +364,7 @@ async fn handle_ping(drk: &DrkPtr) {
 
 /// Auxiliary function to define the completions command handling.
 fn handle_completions(parts: &[&str]) {
+    // Check correct command structure
     if parts.len() != 2 {
         println!("Malformed `completions` command");
         println!("Usage: completions {{shell}}");
@@ -344,6 +376,283 @@ fn handle_completions(parts: &[&str]) {
     }
 }
 
+/// Auxiliary function to define the wallet command handling.
+async fn handle_wallet(drk: &DrkPtr, parts: &[&str]) {
+    // Check correct command structure
+    if parts.len() != 2 && parts.len() != 3 {
+        println!("Malformed `wallet` command");
+        println!("Usage: wallet --{{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}}");
+        return
+    }
+
+    // Handle command flag
+    if parts[1] == "--initialize" {
+        let lock = drk.read().await;
+        if let Err(e) = lock.initialize_wallet().await {
+            println!("Error initializing wallet: {e:?}");
+            return
+        }
+        if let Err(e) = lock.initialize_money().await {
+            println!("Failed to initialize Money: {e:?}");
+            return
+        }
+        if let Err(e) = lock.initialize_dao().await {
+            println!("Failed to initialize DAO: {e:?}");
+            return
+        }
+        if let Err(e) = lock.initialize_deployooor() {
+            println!("Failed to initialize Deployooor: {e:?}");
+        }
+        return
+    }
+
+    if parts[1] == "--keygen" {
+        if let Err(e) = drk.read().await.money_keygen().await {
+            println!("Failed to generate keypair: {e:?}");
+        }
+        return
+    }
+
+    if parts[1] == "--balance" {
+        let lock = drk.read().await;
+        let balmap = match lock.money_balance().await {
+            Ok(m) => m,
+            Err(e) => {
+                println!("Failed to fetch balances map: {e:?}");
+                return
+            }
+        };
+
+        let aliases_map = match lock.get_aliases_mapped_by_token().await {
+            Ok(m) => m,
+            Err(e) => {
+                println!("Failed to fetch aliases map: {e:?}");
+                return
+            }
+        };
+
+        // Create a prettytable with the new data:
+        let mut table = Table::new();
+        table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+        table.set_titles(row!["Token ID", "Aliases", "Balance"]);
+        for (token_id, balance) in balmap.iter() {
+            let aliases = match aliases_map.get(token_id) {
+                Some(a) => a,
+                None => "-",
+            };
+
+            table.add_row(row![
+                token_id,
+                aliases,
+                encode_base10(*balance, BALANCE_BASE10_DECIMALS)
+            ]);
+        }
+
+        if table.is_empty() {
+            println!("No unspent balances found");
+        } else {
+            println!("{table}");
+        }
+        return
+    }
+
+    if parts[1] == "--address" {
+        match drk.read().await.default_address().await {
+            Ok(address) => println!("{address}"),
+            Err(e) => println!("Failed to fetch default address: {e:?}"),
+        }
+        return
+    }
+
+    if parts[1] == "--addresses" {
+        let addresses = match drk.read().await.addresses().await {
+            Ok(a) => a,
+            Err(e) => {
+                println!("Failed to fetch addresses: {e:?}");
+                return
+            }
+        };
+
+        // Create a prettytable with the new data:
+        let mut table = Table::new();
+        table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+        table.set_titles(row!["Key ID", "Public Key", "Secret Key", "Is Default"]);
+        for (key_id, public_key, secret_key, is_default) in addresses {
+            let is_default = match is_default {
+                1 => "*",
+                _ => "",
+            };
+            table.add_row(row![key_id, public_key, secret_key, is_default]);
+        }
+
+        if table.is_empty() {
+            println!("No addresses found");
+        } else {
+            println!("{table}");
+        }
+        return
+    }
+
+    if parts[1] == "--default_address" {
+        if parts.len() != 3 {
+            println!("Malformed `wallet` command");
+            println!("Usage: wallet --default_address {{address_id}}");
+        }
+
+        let idx = match usize::from_str(parts[2]) {
+            Ok(i) => i,
+            Err(e) => {
+                println!("Invalid address id: {e:?}");
+                return
+            }
+        };
+
+        if let Err(e) = drk.read().await.set_default_address(idx) {
+            println!("Failed to set default address: {e:?}");
+        }
+        return
+    }
+
+    if parts[1] == "--secrets" {
+        match drk.read().await.get_money_secrets().await {
+            Ok(secrets) => {
+                for secret in secrets {
+                    println!("{secret}");
+                }
+            }
+            Err(e) => println!("Failed to fetch secrets: {e:?}"),
+        }
+        return
+    }
+
+    if parts[1] == "--import_secrets" {
+        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 {
+                let Ok(bytes) = bs58::decode(&line.trim()).into_vec() else {
+                    println!("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}");
+                    continue
+                };
+                secrets.push(secret);
+            }
+        }
+
+        match drk.read().await.import_money_secrets(secrets).await {
+            Ok(pubkeys) => {
+                for key in pubkeys {
+                    println!("{key}");
+                }
+            }
+            Err(e) => println!("Failed to import secrets: {e:?}"),
+        }
+        return
+    }
+
+    if parts[1] == "--tree" {
+        // TODO: write to a file here not stdout
+        match drk.read().await.get_money_tree().await {
+            Ok(tree) => println!("{tree:#?}"),
+            Err(e) => println!("Failed to fetch tree: {e:?}"),
+        }
+        return
+    }
+
+    if parts[1] == "--coins" {
+        let lock = drk.read().await;
+        let coins = match lock.get_coins(true).await {
+            Ok(c) => c,
+            Err(e) => {
+                println!("Failed to fetch coins: {e:?}");
+                return
+            }
+        };
+
+        if coins.is_empty() {
+            return
+        }
+
+        let aliases_map = match lock.get_aliases_mapped_by_token().await {
+            Ok(m) => m,
+            Err(e) => {
+                println!("Failed to fetch aliases map: {e:?}");
+                return
+            }
+        };
+
+        let mut table = Table::new();
+        table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
+        table.set_titles(row![
+            "Coin",
+            "Token ID",
+            "Aliases",
+            "Value",
+            "Spend Hook",
+            "User Data",
+            "Creation Height",
+            "Spent",
+            "Spent Height",
+            "Spent TX"
+        ]);
+        for coin in coins {
+            let aliases = match aliases_map.get(&coin.0.note.token_id.to_string()) {
+                Some(a) => a,
+                None => "-",
+            };
+
+            let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
+                format!("{}", coin.0.note.spend_hook)
+            } else {
+                String::from("-")
+            };
+
+            let user_data = if coin.0.note.user_data != pallas::Base::ZERO {
+                bs58::encode(&serialize_async(&coin.0.note.user_data).await)
+                    .into_string()
+                    .to_string()
+            } else {
+                String::from("-")
+            };
+
+            let spent_height = match coin.3 {
+                Some(spent_height) => spent_height.to_string(),
+                None => String::from("-"),
+            };
+
+            table.add_row(row![
+                bs58::encode(&serialize_async(&coin.0.coin.inner()).await)
+                    .into_string()
+                    .to_string(),
+                coin.0.note.token_id,
+                aliases,
+                format!(
+                    "{} ({})",
+                    coin.0.note.value,
+                    encode_base10(coin.0.note.value, BALANCE_BASE10_DECIMALS)
+                ),
+                spend_hook,
+                user_data,
+                coin.1,
+                coin.2,
+                spent_height,
+                coin.4,
+            ]);
+        }
+
+        println!("{table}");
+
+        return
+    }
+
+    println!("Malformed `wallet` command");
+    println!("Usage: wallet --{{initialize|keygen|balance|address|addresses|default_address|secrets|import_secrets|tree|coins}}");
+}
+
 /// Auxiliary function to define the subscribe command handling.
 async fn handle_subscribe(
     drk: &DrkPtr,

+ 2 - 2
bin/drk/src/main.rs

@@ -886,12 +886,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
             if coins {
                 let coins = drk.get_coins(true).await?;
 
-                let aliases_map = drk.get_aliases_mapped_by_token().await?;
-
                 if coins.is_empty() {
                     return Ok(())
                 }
 
+                let aliases_map = drk.get_aliases_mapped_by_token().await?;
+
                 let mut table = Table::new();
                 table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
                 table.set_titles(row![