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};
))
.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> {
let addressees = sub_matches
.values_of("to")
.unwrap()
- .map(|s| parse_addressee(s).unwrap())
- .collect();
+ .map(|s| parse_addressee(s))
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|s| Error::Generic(s))?;
let send_all = sub_matches.is_present("send_all");
let fee_rate = sub_matches
.value_of("fee_rate")
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::<u8>::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::<Vec<_>>();
+
+ let init_psbt = psbts.pop().unwrap();
+ let final_psbt = psbts
+ .into_iter()
+ .try_fold::<_, _, Result<PartiallySignedTransaction, Error>>(
+ init_psbt,
+ |mut acc, x| {
+ acc.merge(x)?;
+ Ok(acc)
+ },
+ )?;
+
+ Ok(Some(format!(
+ "PSBT: {}",
+ base64::encode(&serialize(&final_psbt))
+ )))
} else {
Ok(None)
}
}
// attempt to finalize
- let finalized = self.finalize_psbt(tx.clone(), &mut psbt, assume_height)?;
+ let finalized = self.finalize_psbt(&mut psbt, assume_height)?;
Ok((psbt, finalized))
}
}
}
+ pub fn finalize_psbt(
+ &self,
+ psbt: &mut PSBT,
+ assume_height: Option<u32>,
+ ) -> Result<bool, Error> {
+ let mut tx = psbt.global.unsigned_tx.clone();
+
+ for (n, input) in tx.input.iter_mut().enumerate() {
+ // safe to run only on the descriptor because we assume the change descriptor also has
+ // the same structure
+ let desc = self.descriptor.derive_from_psbt_input(psbt, n);
+ debug!("{:?}", psbt.inputs[n].hd_keypaths);
+ debug!("reconstructed descriptor is {:?}", desc);
+
+ let desc = match desc {
+ Err(_) => return Ok(false),
+ Ok(desc) => desc,
+ };
+
+ // if the height is None in the database it means it's still unconfirmed, so consider
+ // that as a very high value
+ let create_height = self
+ .database
+ .borrow()
+ .get_tx(&input.previous_output.txid, false)?
+ .and_then(|tx| Some(tx.height.unwrap_or(std::u32::MAX)));
+ let current_height = assume_height.or(self.current_height);
+
+ debug!(
+ "Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
+ n, input.previous_output, create_height, current_height
+ );
+
+ // TODO: use height once we sync headers
+ let satisfier =
+ PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height);
+
+ match desc.satisfy(input, satisfier) {
+ Ok(_) => continue,
+ Err(e) => {
+ debug!("satisfy error {:?} for input {}", e, n);
+ return Ok(false);
+ }
+ }
+ }
+
+ // consume tx to extract its input's script_sig and witnesses and move them into the psbt
+ for (input, psbt_input) in tx.input.into_iter().zip(psbt.inputs.iter_mut()) {
+ psbt_input.final_script_sig = Some(input.script_sig);
+ psbt_input.final_script_witness = Some(input.witness);
+ }
+
+ Ok(true)
+ }
+
// Internals
#[cfg(not(target_arch = "wasm32"))]
Ok((answer, paths, selected_amount, fee_val))
}
-
- fn finalize_psbt(
- &self,
- mut tx: Transaction,
- psbt: &mut PSBT,
- assume_height: Option<u32>,
- ) -> Result<bool, Error> {
- for (n, input) in tx.input.iter_mut().enumerate() {
- // safe to run only on the descriptor because we assume the change descriptor also has
- // the same structure
- let desc = self.descriptor.derive_from_psbt_input(psbt, n);
- debug!("{:?}", psbt.inputs[n].hd_keypaths);
- debug!("reconstructed descriptor is {:?}", desc);
-
- let desc = match desc {
- Err(_) => return Ok(false),
- Ok(desc) => desc,
- };
-
- // if the height is None in the database it means it's still unconfirmed, so consider
- // that as a very high value
- let create_height = self
- .database
- .borrow()
- .get_tx(&input.previous_output.txid, false)?
- .and_then(|tx| Some(tx.height.unwrap_or(std::u32::MAX)));
- let current_height = assume_height.or(self.current_height);
-
- debug!(
- "Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
- n, input.previous_output, create_height, current_height
- );
-
- // TODO: use height once we sync headers
- let satisfier =
- PSBTSatisfier::new(&psbt.inputs[n], false, create_height, current_height);
-
- match desc.satisfy(input, satisfier) {
- Ok(_) => continue,
- Err(e) => {
- debug!("satisfy error {:?} for input {}", e, n);
- return Ok(false);
- }
- }
- }
-
- // consume tx to extract its input's script_sig and witnesses and move them into the psbt
- for (input, psbt_input) in tx.input.into_iter().zip(psbt.inputs.iter_mut()) {
- psbt_input.final_script_sig = Some(input.script_sig);
- psbt_input.final_script_witness = Some(input.witness);
- }
-
- Ok(true)
- }
}
impl<B, D> Wallet<B, D>
.await
}
- pub async fn broadcast(&self, psbt: PSBT) -> Result<(Txid, Transaction), Error> {
- let extracted = psbt.extract_tx();
- self.client.borrow_mut().broadcast(&extracted).await?;
+ pub async fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
+ self.client.borrow_mut().broadcast(&tx).await?;
- Ok((extracted.txid(), extracted))
+ Ok(tx.txid())
}
}