use core::fmt::{Debug, Display};
use std::vec::Vec;
use bdk_coin_select::FeeRate;
use bitcoin::{absolute, transaction, Sequence};
use miniscript::bitcoin;
use miniscript::psbt::PsbtExt;
use crate::{Finalizer, Input, Output};
const FALLBACK_SEQUENCE: bitcoin::Sequence = bitcoin::Sequence::ENABLE_LOCKTIME_NO_RBF;
pub(crate) fn cs_feerate(feerate: bitcoin::FeeRate) -> bdk_coin_select::FeeRate {
FeeRate::from_sat_per_wu(feerate.to_sat_per_kwu() as f32 / 1000.0)
}
#[derive(Debug, Clone)]
pub struct Selection {
pub inputs: Vec<Input>,
pub outputs: Vec<Output>,
}
#[derive(Debug, Clone)]
pub struct PsbtParams {
pub version: transaction::Version,
pub fallback_locktime: absolute::LockTime,
pub fallback_sequence: Sequence,
pub mandate_full_tx_for_segwit_v0: bool,
}
impl Default for PsbtParams {
fn default() -> Self {
Self {
version: transaction::Version::TWO,
fallback_locktime: absolute::LockTime::ZERO,
fallback_sequence: FALLBACK_SEQUENCE,
mandate_full_tx_for_segwit_v0: true,
}
}
}
#[derive(Debug)]
pub enum CreatePsbtError {
LockTypeMismatch,
MissingFullTxForLegacyInput(Input),
MissingFullTxForSegwitV0Input(Input),
Psbt(bitcoin::psbt::Error),
OutputUpdate(miniscript::psbt::OutputUpdateError),
}
impl core::fmt::Display for CreatePsbtError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CreatePsbtError::LockTypeMismatch => write!(f, "cannot mix locktime units"),
CreatePsbtError::MissingFullTxForLegacyInput(input) => write!(
f,
"legacy input that spends {} requires PSBT_IN_NON_WITNESS_UTXO",
input.prev_outpoint()
),
CreatePsbtError::MissingFullTxForSegwitV0Input(input) => write!(
f,
"segwit v0 input that spends {} requires PSBT_IN_NON_WITNESS_UTXO",
input.prev_outpoint()
),
CreatePsbtError::Psbt(error) => Display::fmt(&error, f),
CreatePsbtError::OutputUpdate(output_update_error) => {
Display::fmt(&output_update_error, f)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CreatePsbtError {}
impl Selection {
fn _accumulate_max_locktime(
locktimes: impl IntoIterator<Item = absolute::LockTime>,
fallback: absolute::LockTime,
) -> Option<absolute::LockTime> {
let mut acc = Option::<absolute::LockTime>::None;
for locktime in locktimes {
match &mut acc {
Some(acc) => {
if !acc.is_same_unit(locktime) {
return None;
}
if acc.is_implied_by(locktime) {
*acc = locktime;
}
}
acc => *acc = Some(locktime),
};
}
if acc.is_none() {
acc = Some(fallback);
}
acc
}
pub fn create_psbt(&self, params: PsbtParams) -> Result<bitcoin::Psbt, CreatePsbtError> {
let mut psbt = bitcoin::Psbt::from_unsigned_tx(bitcoin::Transaction {
version: params.version,
lock_time: Self::_accumulate_max_locktime(
self.inputs
.iter()
.filter_map(|input| input.absolute_timelock()),
params.fallback_locktime,
)
.ok_or(CreatePsbtError::LockTypeMismatch)?,
input: self
.inputs
.iter()
.map(|input| bitcoin::TxIn {
previous_output: input.prev_outpoint(),
sequence: input.sequence().unwrap_or(params.fallback_sequence),
..Default::default()
})
.collect(),
output: self.outputs.iter().map(|output| output.txout()).collect(),
})
.map_err(CreatePsbtError::Psbt)?;
for (plan_input, psbt_input) in self.inputs.iter().zip(psbt.inputs.iter_mut()) {
if let Some(finalized_psbt_input) = plan_input.psbt_input() {
*psbt_input = finalized_psbt_input.clone();
continue;
}
if let Some(plan) = plan_input.plan() {
plan.update_psbt_input(psbt_input);
let witness_version = plan.witness_version();
if witness_version.is_some() {
psbt_input.witness_utxo = Some(plan_input.prev_txout().clone());
}
psbt_input.non_witness_utxo = plan_input.prev_tx().cloned();
if psbt_input.non_witness_utxo.is_none() {
if witness_version.is_none() {
return Err(CreatePsbtError::MissingFullTxForLegacyInput(
plan_input.clone(),
));
}
if params.mandate_full_tx_for_segwit_v0
&& witness_version == Some(bitcoin::WitnessVersion::V0)
{
return Err(CreatePsbtError::MissingFullTxForSegwitV0Input(
plan_input.clone(),
));
}
}
continue;
}
unreachable!("input candidate must either have finalized psbt input or plan");
}
for (output_index, output) in self.outputs.iter().enumerate() {
if let Some(desc) = output.descriptor() {
psbt.update_output_with_descriptor(output_index, desc)
.map_err(CreatePsbtError::OutputUpdate)?;
}
}
Ok(psbt)
}
pub fn into_finalizer(self) -> Finalizer {
Finalizer::new(
self.inputs
.iter()
.filter_map(|input| Some((input.prev_outpoint(), input.plan().cloned()?))),
)
}
}