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};
pub struct RbfSet {
txs: HashMap<Txid, Arc<Transaction>>,
prev_txouts: HashMap<OutPoint, TxOut>,
}
#[derive(Debug)]
pub struct OriginalTxHasNoInputsAvailable {
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 {
pub fn new<T, O>(txs: T, prev_txouts: O) -> Option<Self>
where
T: IntoIterator,
T::Item: Into<Arc<Transaction>>,
O: IntoIterator<Item = (OutPoint, TxOut)>,
{
let set = Self {
txs: txs
.into_iter()
.map(|tx| {
let tx: Arc<Transaction> = tx.into();
(tx.compute_txid(), tx)
})
.collect(),
prev_txouts: prev_txouts.into_iter().collect(),
};
let no_missing_previous_txouts = set
.txs
.values()
.flat_map(|tx| tx.input.iter().map(|txin| txin.previous_output))
.all(|op: OutPoint| set.prev_txouts.contains_key(&op));
if no_missing_previous_txouts {
Some(set)
} else {
None
}
}
pub fn txids(&self) -> impl ExactSizeIterator<Item = Txid> + '_ {
self.txs.keys().copied()
}
pub fn contains_tx(&self, txid: Txid) -> bool {
self.txs.contains_key(&txid)
}
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
}
}
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 self.txs.contains_key(&spend.txid) {
continue;
}
let txout = self.prev_txouts.get(&spend).expect("must have prev txout");
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();
input_sum - output_sum
}
pub fn selector_rbf_params(&self) -> RbfParams {
RbfParams::new(self.txs.values().map(|tx| (tx.as_ref(), self._fee(tx))))
}
}