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

This commit is contained in:
skoupidi
2026-01-01 19:14:21 +02:00
parent 965870ab9b
commit e460c39e6c
3 changed files with 127 additions and 23 deletions

View File

@@ -55,22 +55,6 @@ pub async fn parse_tx_from_stdin() -> Result<Transaction> {
}
}
/// 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
/// provided input or fallback to stdin if its empty.
pub async fn parse_tx_from_input(input: &[String]) -> Result<Transaction> {
@@ -84,6 +68,36 @@ pub async fn parse_tx_from_input(input: &[String]) -> Result<Transaction> {
}
}
/// 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 base64 encoded contract calls from
/// provided input or fallback to stdin if its empty.
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.
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<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");

View File

@@ -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 {

View File

@@ -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);
}
},