bdk_tx 0.2.0

Bitcoin transaction building library.
Documentation
use alloc::sync::Arc;
use core::fmt::Display;

use bitcoin::{absolute, Amount, OutPoint, Transaction, TxOut, Txid};
use miniscript::bitcoin;

use crate::collections::{HashMap, HashSet};
use crate::{CanonicalUnspents, Input, RbfParams};

/// Set of txs to replace.
pub struct RbfSet {
    /// Original txs the replacement must conflict with.
    txs: HashMap<Txid, Arc<Transaction>>,
    /// Sum of fees from evicted descendants, counted toward the replacement fee floor.
    descendant_fee: Amount,
    /// Previous outputs spent by the originals.
    prev_txouts: HashMap<OutPoint, TxOut>,
}

/// Occurs when the given original tx has no input spend that is still available for spending.
#[derive(Debug)]
pub struct OriginalTxHasNoInputsAvailable {
    /// Original txid.
    pub txid: Txid,
}

impl Display for OriginalTxHasNoInputsAvailable {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(
            f,
            "original tx {} has no input spend that is still available",
            self.txid
        )
    }
}

#[cfg(feature = "std")]
impl std::error::Error for OriginalTxHasNoInputsAvailable {}

impl RbfSet {
    /// Create.
    ///
    /// Returns `None` if we have missing `prev_txouts` for the `txs`.
    ///
    /// `txs` are the original transactions that are to be replaced.
    /// `descendant_fee` is the sum of fees from evicted descendants, counted toward the
    /// replacement fee floor.
    pub fn new<T, O>(txs: T, descendant_fee: Amount, prev_txouts: O) -> Option<Self>
    where
        T: IntoIterator<Item: Into<Arc<Transaction>>>,
        O: IntoIterator<Item = (OutPoint, TxOut)>,
    {
        let txs = txs
            .into_iter()
            .map(|tx| {
                let tx: Arc<Transaction> = tx.into();
                (tx.compute_txid(), tx)
            })
            .collect::<HashMap<_, _>>();

        let rbf_set = Self {
            txs,
            descendant_fee,
            prev_txouts: prev_txouts.into_iter().collect(),
        };
        let no_missing_prevouts = rbf_set.txs.values().all(|tx| {
            tx.input
                .iter()
                .all(|txin| rbf_set.prev_txouts.contains_key(&txin.previous_output))
        });
        if !no_missing_prevouts {
            return None;
        }

        Some(rbf_set)
    }

    /// Txids of the original txs that are to be replaced.
    pub fn txids(&self) -> impl ExactSizeIterator<Item = Txid> + '_ {
        self.txs.keys().copied()
    }

    /// Whether `txid` is an original tx that is to be replaced.
    pub fn contains_tx(&self, txid: Txid) -> bool {
        self.txs.contains_key(&txid)
    }

    /// Filters input candidates according to rule 2.
    ///
    /// According to rule 2, we cannot spend unconfirmed txs in the replacement unless it
    /// was a spend that was already part of the original tx.
    pub fn candidate_filter(&self, tip_height: absolute::Height) -> impl Fn(&Input) -> bool + '_ {
        let prev_spends = self
            .txs
            .values()
            .flat_map(|tx| tx.input.iter().map(|txin| txin.previous_output))
            .collect::<HashSet<OutPoint>>();
        move |input| {
            prev_spends.contains(&input.prev_outpoint()) || input.confirmations(tip_height) > 0
        }
    }

    /// Tries to find the largest input per original tx.
    ///
    /// The returned outpoints can be used to create the `must_select` inputs to pass into
    /// `InputCandidates`. This guarantees that the all transactions within this set gets replaced.
    pub fn must_select_largest_input_of_each_original_tx(
        &self,
        canon_utxos: &CanonicalUnspents,
    ) -> Result<HashSet<OutPoint>, OriginalTxHasNoInputsAvailable> {
        let mut must_select = HashSet::new();

        for original_tx in self.txs.values() {
            let mut largest_value = Amount::ZERO;
            let mut largest_spend = Option::<OutPoint>::None;
            let original_tx_spends = original_tx.input.iter().map(|txin| txin.previous_output);
            for spend in original_tx_spends {
                // If this spends from another original tx , we do not consider it as replacing
                // the parent will replace this one.
                if self.txs.contains_key(&spend.txid) {
                    continue;
                }
                let txout = self.prev_txouts.get(&spend).expect("must have prev txout");

                // not available
                if !canon_utxos.is_unspent(spend) {
                    continue;
                }

                if txout.value > largest_value {
                    largest_value = txout.value;
                    largest_spend = Some(spend);
                }
            }
            let largest_spend = largest_spend.ok_or(OriginalTxHasNoInputsAvailable {
                txid: original_tx.compute_txid(),
            })?;
            must_select.insert(largest_spend);
        }

        Ok(must_select)
    }

    fn _fee(&self, tx: &Transaction) -> Amount {
        let output_sum: Amount = tx.output.iter().map(|txout| txout.value).sum();
        let input_sum: Amount = tx
            .input
            .iter()
            .map(|txin| {
                self.prev_txouts
                    .get(&txin.previous_output)
                    .expect("prev output must exist")
                    .value
            })
            .sum();
        // TODO: is it safe to do unchecked subtraction?
        input_sum - output_sum
    }

    /// Coin selector RBF parameters.
    pub fn selector_rbf_params(&self) -> RbfParams {
        RbfParams::new(
            self.txs.values().map(|tx| (tx.as_ref(), self._fee(tx))),
            self.descendant_fee,
        )
    }
}