From: Alekos Filini Date: Sun, 17 May 2020 16:01:52 +0000 (+0200) Subject: [cli] Add a few commands to handle psbts X-Git-Tag: 0.1.0-beta.1~25 X-Git-Url: http://internal-gitweb-vhost/script/%22https:/database/scripts/struct.CommandStringError.html?a=commitdiff_plain;h=791d1c66f91edce9ee05e1113338c9f422479147;p=bdk-cli [cli] Add a few commands to handle psbts --- diff --git a/src/cli.rs b/src/cli.rs index 3dd23c0..a6f3462 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,6 +7,7 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use log::{debug, error, info, trace, LevelFilter}; use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex}; +use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::util::psbt::PartiallySignedTransaction; use bitcoin::{Address, OutPoint}; @@ -147,16 +148,69 @@ pub fn make_cli_subcommands<'a, 'b>() -> App<'a, 'b> { )) .subcommand( SubCommand::with_name("broadcast") - .about("Extracts the finalized transaction from a PSBT and broadcasts it to the network") + .about("Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract") .arg( Arg::with_name("psbt") .long("psbt") .value_name("BASE64_PSBT") - .help("Sets the PSBT to broadcast") + .help("Sets the PSBT to extract and broadcast") + .takes_value(true) + .required_unless("tx") + .number_of_values(1)) + .arg( + Arg::with_name("tx") + .long("tx") + .value_name("RAWTX") + .help("Sets the raw transaction to broadcast") + .takes_value(true) + .required_unless("psbt") + .number_of_values(1)) + ) + .subcommand( + SubCommand::with_name("extract_psbt") + .about("Extracts a raw transaction from a PSBT") + .arg( + Arg::with_name("psbt") + .long("psbt") + .value_name("BASE64_PSBT") + .help("Sets the PSBT to extract") + .takes_value(true) + .required(true) + .number_of_values(1)) + ) + .subcommand( + SubCommand::with_name("finalize_psbt") + .about("Finalizes a psbt") + .arg( + Arg::with_name("psbt") + .long("psbt") + .value_name("BASE64_PSBT") + .help("Sets the PSBT to finalize") + .takes_value(true) + .required(true) + .number_of_values(1)) + .arg( + Arg::with_name("assume_height") + .long("assume_height") + .value_name("HEIGHT") + .help("Assume the blockchain has reached a specific height") .takes_value(true) .number_of_values(1) - .required(true), - )) + .required(false)) + ) + .subcommand( + SubCommand::with_name("combine_psbt") + .about("Combines multiple PSBTs into one") + .arg( + Arg::with_name("psbt") + .long("psbt") + .value_name("BASE64_PSBT") + .help("Add one PSBT to comine. This option can be repeated multiple times, one for each PSBT") + .takes_value(true) + .number_of_values(1) + .required(true) + .multiple(true)) + ) } pub fn add_global_flags<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { @@ -240,8 +294,9 @@ where let addressees = sub_matches .values_of("to") .unwrap() - .map(|s| parse_addressee(s).unwrap()) - .collect(); + .map(|s| parse_addressee(s)) + .collect::, _>>() + .map_err(|s| Error::Generic(s))?; let send_all = sub_matches.is_present("send_all"); let fee_rate = sub_matches .value_of("fee_rate") @@ -308,11 +363,73 @@ where Ok(Some(res)) } else if let Some(sub_matches) = matches.subcommand_matches("broadcast") { - let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap(); - let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); - let (txid, _) = wallet.broadcast(psbt).await?; + let tx = if sub_matches.value_of("psbt").is_some() { + let psbt = base64::decode(&sub_matches.value_of("psbt").unwrap()).unwrap(); + let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); + psbt.extract_tx() + } else if sub_matches.value_of("tx").is_some() { + deserialize(&Vec::::from_hex(&sub_matches.value_of("tx").unwrap()).unwrap()) + .unwrap() + } else { + panic!("Missing `psbt` and `tx` option"); + }; + + let txid = wallet.broadcast(tx).await?; Ok(Some(format!("TXID: {}", txid))) + } else if let Some(sub_matches) = matches.subcommand_matches("extract_psbt") { + let psbt = base64::decode(&sub_matches.value_of("psbt").unwrap()).unwrap(); + let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); + + Ok(Some(format!( + "TX: {}", + serialize(&psbt.extract_tx()).to_hex() + ))) + } else if let Some(sub_matches) = matches.subcommand_matches("finalize_psbt") { + let psbt = base64::decode(&sub_matches.value_of("psbt").unwrap()).unwrap(); + let mut psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); + + let assume_height = sub_matches + .value_of("assume_height") + .and_then(|s| Some(s.parse().unwrap())); + + let finalized = wallet.finalize_psbt(&mut psbt, assume_height)?; + + let mut res = String::new(); + res += &format!("PSBT: {}\n", base64::encode(&serialize(&psbt))); + res += &format!("Finalized: {}", finalized); + if finalized { + res += &format!("\nExtracted: {}", serialize_hex(&psbt.extract_tx())); + } + + Ok(Some(res)) + } else if let Some(sub_matches) = matches.subcommand_matches("combine_psbt") { + let mut psbts = sub_matches + .values_of("psbt") + .unwrap() + .map(|s| { + let psbt = base64::decode(&s).unwrap(); + let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap(); + + psbt + }) + .collect::>(); + + let init_psbt = psbts.pop().unwrap(); + let final_psbt = psbts + .into_iter() + .try_fold::<_, _, Result>( + init_psbt, + |mut acc, x| { + acc.merge(x)?; + Ok(acc) + }, + )?; + + Ok(Some(format!( + "PSBT: {}", + base64::encode(&serialize(&final_psbt)) + ))) } else { Ok(None) }