Jelajahi Sumber

drk/interactive: added missing tx-from-calls subcommand

skoupidi 7 bulan lalu
induk
melakukan
e460c39e6c
3 mengubah file dengan 124 tambahan dan 20 penghapusan
  1. 32 14
      bin/drk/src/cli_util.rs
  2. 85 2
      bin/drk/src/interactive.rs
  3. 7 4
      bin/drk/src/main.rs

+ 32 - 14
bin/drk/src/cli_util.rs

@@ -55,33 +55,47 @@ pub async fn parse_tx_from_stdin() -> Result<Transaction> {
     }
 }
 
-/// Auxiliary function to parse base64-encoded contract calls from stdin.
+/// 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<Transaction> {
+    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 base64 encoded contract calls from stdin.
 pub async fn parse_calls_from_stdin() -> Result<Vec<ContractCallImport>> {
     let lines = stdin().lines();
-
     let mut calls = vec![];
-
     for line in lines {
         let Some(line) = base64::decode(&line?) else {
             return Err(Error::ParseFailed("Failed to decode base64"))
         };
         calls.push(deserialize_async(&line).await?);
     }
-
     Ok(calls)
 }
 
-/// Auxiliary function to parse a base64 encoded transaction from
+/// Auxiliary function to parse base64 encoded contract calls from
 /// provided input or fallback to stdin if its empty.
-pub async fn parse_tx_from_input(input: &[String]) -> Result<Transaction> {
-    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")),
+pub async fn parse_calls_from_input(input: &[String]) -> Result<Vec<ContractCallImport>> {
+    if input.is_empty() {
+        return parse_calls_from_stdin().await
     }
+
+    let mut calls = vec![];
+    for line in input {
+        let Some(line) = base64::decode(line) else {
+            return Err(Error::ParseFailed("Failed to decode base64"))
+        };
+        calls.push(deserialize_async(&line).await?);
+    }
+    Ok(calls)
 }
 
 /// Auxiliary function to parse provided string into a values pair.
@@ -391,8 +405,12 @@ pub fn generate_completions(shell: &str) -> Result<String> {
         .about("Attach the fee call to a transaction given from stdin");
 
     // TxFromCalls
+    let calls_map =
+        Arg::with_name("calls-map").help("Optional parent/children dependency map for the calls");
+
     let tx_from_calls = SubCommand::with_name("tx-from-calls")
-        .about("Create a transaction from newline-separated calls from stdin");
+        .about("Create a transaction from newline-separated calls from stdin")
+        .args(&[calls_map]);
 
     // Inspect
     let inspect = SubCommand::with_name("inspect").about("Inspect a transaction from stdin");

+ 85 - 2
bin/drk/src/interactive.rs

@@ -60,8 +60,8 @@ use darkfi_serial::{deserialize_async, serialize_async};
 use crate::{
     cli_util::{
         append_or_print, display_mining_config, generate_completions, kaching,
-        parse_mining_config_from_input, parse_token_pair, parse_tx_from_input, parse_value_pair,
-        print_output,
+        parse_calls_from_input, parse_mining_config_from_input, parse_token_pair, parse_tree,
+        parse_tx_from_input, parse_value_pair, print_output, tx_from_calls_mapped,
     },
     common::*,
     dao::{DaoParams, ProposalRecord},
@@ -94,6 +94,9 @@ fn help(output: &mut Vec<String>) {
     output.push(String::from("\tdao: DAO functionalities"));
     output
         .push(String::from("\tattach-fee: Attach the fee call to a transaction given from stdin"));
+    output.push(String::from(
+        "\ttx-from-calls: Create a transaction from newline-separated calls from stdin",
+    ));
     output.push(String::from("\tinspect: Inspect a transaction from stdin"));
     output.push(String::from("\tbroadcast: Read a transaction from stdin and broadcast it"));
     output.push(String::from(
@@ -207,6 +210,11 @@ fn completion(buffer: &str, lc: &mut Vec<String>) {
         return
     }
 
+    if last.starts_with("tx") {
+        lc.push(prefix + "tx-from-calls");
+        return
+    }
+
     if last.starts_with("i") {
         lc.push(prefix + "inspect");
         return
@@ -378,6 +386,7 @@ fn hints(buffer: &str) -> Option<(String, i32, bool)> {
         "contract export-data " => Some(("<tx-hash>".to_string(), color, bold)),
         "contract deploy " => Some(("<deploy-auth> <wasm-path> [deploy-ix]".to_string(), color, bold)),
         "contract lock " => Some(("<deploy-auth>".to_string(), color, bold)),
+        "tx-from-calls " => Some(("[calls-map]".to_string(), color, bold)),
         _ => None,
     }
 }
@@ -536,6 +545,7 @@ pub async fn interactive(
                 "otc" => handle_otc(drk, &parts, &input, &mut output).await,
                 "dao" => handle_dao(drk, &parts, &input, &mut output).await,
                 "attach-fee" => handle_attach_fee(drk, &input, &mut output).await,
+                "tx-from-calls" => handle_tx_from_calls(&parts, &input, &mut output).await,
                 "inspect" => handle_inspect(&input, &mut output).await,
                 "broadcast" => handle_broadcast(drk, &input, &mut output).await,
                 "subscribe" => {
@@ -2341,6 +2351,79 @@ async fn handle_attach_fee(drk: &DrkPtr, input: &[String], output: &mut Vec<Stri
     }
 }
 
+/// Auxiliary function to define the tx from calls command handling.
+async fn handle_tx_from_calls(parts: &[&str], input: &[String], output: &mut Vec<String>) {
+    // Check correct subcommand structure
+    if parts.len() != 1 && parts.len() != 2 {
+        output.push(String::from("Malformed `tx-from-calls` subcommand"));
+        output.push(String::from("Usage: tx-from-calls [calls-map]"));
+        return
+    }
+
+    // Parse calls
+    let calls = match parse_calls_from_input(input).await {
+        Ok(c) => c,
+        Err(e) => {
+            output.push(format!("Error while parsing transaction calls: {e}"));
+            return
+        }
+    };
+    if calls.is_empty() {
+        output.push(String::from("No calls were parsed"));
+        return
+    }
+
+    // If there is a given map, parse it, otherwise construct a
+    // linear map.
+    let calls_map = if parts.len() == 2 {
+        match parse_tree(parts[1]) {
+            Ok(m) => m,
+            Err(e) => {
+                output.push(format!("Failed parsing calls map: {e}"));
+                return
+            }
+        }
+    } else {
+        let mut calls_map = Vec::with_capacity(calls.len());
+        for (i, _) in calls.iter().enumerate() {
+            calls_map.push((i, vec![]));
+        }
+        calls_map
+    };
+    if calls_map.len() != calls.len() {
+        output.push(String::from("Calls map size not equal to parsed calls"));
+        return
+    }
+
+    // Create a transaction from the mapped calls.
+    let (mut tx_builder, signature_secrets) = match tx_from_calls_mapped(&calls, &calls_map) {
+        Ok(pair) => pair,
+        Err(e) => {
+            output.push(format!("Failed to create a transaction from the mapped calls: {e}"));
+            return
+        }
+    };
+
+    // Now build and sign the tx
+    let mut tx = match tx_builder.build() {
+        Ok(tx) => tx,
+        Err(e) => {
+            output.push(format!("Failed to build the transaction: {e}"));
+            return
+        }
+    };
+    let sigs = match tx.create_sigs(&signature_secrets) {
+        Ok(s) => s,
+        Err(e) => {
+            output.push(format!("Failed to create the transaction signatures: {e}"));
+            return
+        }
+    };
+    tx.signatures.push(sigs);
+
+    output.push(base64::encode(&serialize_async(&tx).await));
+}
+
 /// Auxiliary function to define the inspect command handling.
 async fn handle_inspect(input: &[String], output: &mut Vec<String>) {
     match parse_tx_from_input(input).await {

+ 7 - 4
bin/drk/src/main.rs

@@ -180,8 +180,7 @@ enum Subcmd {
 
     /// Create a transaction from newline-separated calls from stdin
     TxFromCalls {
-        #[structopt(long = "map")]
-        /// The parent/children dependency map for the calls
+        /// Optional parent/children dependency map for the calls
         calls_map: Option<String>,
     },
 
@@ -2024,8 +2023,12 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
         }
 
         Subcmd::TxFromCalls { calls_map } => {
+            // Parse calls
             let calls = parse_calls_from_stdin().await?;
-            assert!(!calls.is_empty());
+            if calls.is_empty() {
+                eprintln!("No calls were parsed");
+                exit(1);
+            }
 
             // If there is a given map, parse it, otherwise construct a
             // linear map.
@@ -2033,7 +2036,7 @@ async fn realmain(args: Args, ex: ExecutorPtr) -> Result<()> {
                 Some(cmap) => match parse_tree(&cmap) {
                     Ok(v) => v,
                     Err(e) => {
-                        eprintln!("Failed parsing calls map: {}", e);
+                        eprintln!("Failed parsing calls map: {e}");
                         exit(1);
                     }
                 },