From e460c39e6ce85b20bca68568fa02d37e9d992dea Mon Sep 17 00:00:00 2001 From: skoupidi Date: Thu, 1 Jan 2026 19:14:21 +0200 Subject: [PATCH] drk/interactive: added missing tx-from-calls subcommand --- bin/drk/src/cli_util.rs | 52 +++++++++++++++-------- bin/drk/src/interactive.rs | 87 +++++++++++++++++++++++++++++++++++++- bin/drk/src/main.rs | 11 +++-- 3 files changed, 127 insertions(+), 23 deletions(-) diff --git a/bin/drk/src/cli_util.rs b/bin/drk/src/cli_util.rs index b009caa85..7162f7232 100644 --- a/bin/drk/src/cli_util.rs +++ b/bin/drk/src/cli_util.rs @@ -55,22 +55,6 @@ pub async fn parse_tx_from_stdin() -> Result { } } -/// Auxiliary function to parse base64-encoded contract calls from stdin. -pub async fn parse_calls_from_stdin() -> Result> { - 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 /// provided input or fallback to stdin if its empty. pub async fn parse_tx_from_input(input: &[String]) -> Result { @@ -84,6 +68,36 @@ pub async fn parse_tx_from_input(input: &[String]) -> Result { } } +/// Auxiliary function to parse base64 encoded contract calls from stdin. +pub async fn parse_calls_from_stdin() -> Result> { + 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 base64 encoded contract calls from +/// provided input or fallback to stdin if its empty. +pub async fn parse_calls_from_input(input: &[String]) -> Result> { + 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. pub fn parse_value_pair(s: &str) -> Result<(u64, u64)> { let v: Vec<&str> = s.split(':').collect(); @@ -391,8 +405,12 @@ pub fn generate_completions(shell: &str) -> Result { .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"); diff --git a/bin/drk/src/interactive.rs b/bin/drk/src/interactive.rs index 39a7f58bc..57c0b291f 100644 --- a/bin/drk/src/interactive.rs +++ b/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) { 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) { 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(("".to_string(), color, bold)), "contract deploy " => Some((" [deploy-ix]".to_string(), color, bold)), "contract lock " => Some(("".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) { + // 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) { match parse_tx_from_input(input).await { diff --git a/bin/drk/src/main.rs b/bin/drk/src/main.rs index 97d9c09ed..3283048fe 100644 --- a/bin/drk/src/main.rs +++ b/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, }, @@ -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); } },