|
| 1 | +use alloc::vec::Vec; |
| 2 | +use bitcoin::{absolute, Amount, OutPoint, Transaction, TxOut, Txid}; |
| 3 | +use miniscript::bitcoin; |
| 4 | + |
| 5 | +use crate::collections::{HashMap, HashSet}; |
| 6 | +use crate::{InputCandidates, InputGroup, RbfParams}; |
| 7 | + |
| 8 | +/// Set of txs to replace. |
| 9 | +pub struct RbfSet<'t> { |
| 10 | + txs: HashMap<Txid, &'t Transaction>, |
| 11 | + prev_txouts: HashMap<OutPoint, TxOut>, |
| 12 | +} |
| 13 | + |
| 14 | +impl<'t> RbfSet<'t> { |
| 15 | + /// Create. |
| 16 | + /// |
| 17 | + /// Returns `None` if we have missing `prev_txouts` for the `txs`. |
| 18 | + /// |
| 19 | + /// If any transactions in `txs` are ancestors or descendants of others in `txs`, be sure to |
| 20 | + /// include any intermediary transactions needed to resolve those dependencies. |
| 21 | + /// |
| 22 | + /// TODO: Error if trying to replace coinbase. |
| 23 | + pub fn new<T, O>(txs: T, prev_txouts: O) -> Option<Self> |
| 24 | + where |
| 25 | + T: IntoIterator<Item = &'t Transaction>, |
| 26 | + O: IntoIterator<Item = (OutPoint, TxOut)>, |
| 27 | + { |
| 28 | + let set = Self { |
| 29 | + txs: txs.into_iter().map(|tx| (tx.compute_txid(), tx)).collect(), |
| 30 | + prev_txouts: prev_txouts.into_iter().collect(), |
| 31 | + }; |
| 32 | + let no_missing_previous_txouts = set |
| 33 | + .txs |
| 34 | + .values() |
| 35 | + .flat_map(|tx| tx.input.iter().map(|txin| txin.previous_output)) |
| 36 | + .all(|op: OutPoint| set.prev_txouts.contains_key(&op)); |
| 37 | + if no_missing_previous_txouts { |
| 38 | + Some(set) |
| 39 | + } else { |
| 40 | + None |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + /// Txids of the original txs that are to be replaced. |
| 45 | + /// |
| 46 | + /// Used for modifying canonicalization to exclude the original txs. |
| 47 | + pub fn txids(&self) -> impl ExactSizeIterator<Item = Txid> + '_ { |
| 48 | + self.txs.keys().copied() |
| 49 | + } |
| 50 | + |
| 51 | + /// Filters input candidates according to rule 2. |
| 52 | + /// |
| 53 | + /// According to rule 2, we cannot spend unconfirmed txs in the replacement unless it |
| 54 | + /// was a spend that was already part of the original tx. |
| 55 | + pub fn candidate_filter( |
| 56 | + &self, |
| 57 | + tip_height: absolute::Height, |
| 58 | + ) -> impl Fn(&InputGroup) -> bool + '_ { |
| 59 | + let prev_spends = self |
| 60 | + .txs |
| 61 | + .values() |
| 62 | + .flat_map(|tx| { |
| 63 | + tx.input |
| 64 | + .iter() |
| 65 | + .map(|txin| txin.previous_output) |
| 66 | + .collect::<Vec<_>>() |
| 67 | + }) |
| 68 | + .collect::<HashSet<OutPoint>>(); |
| 69 | + move |group| { |
| 70 | + group.all(|input| { |
| 71 | + prev_spends.contains(&input.prev_outpoint()) || input.confirmations(tip_height) > 0 |
| 72 | + }) |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + /// Returns a policy that selects the largest input of every original tx. |
| 77 | + /// |
| 78 | + /// This guarantees that the txs are replaced. |
| 79 | + pub fn must_select_largest_input_per_tx( |
| 80 | + &self, |
| 81 | + ) -> impl FnMut(&InputCandidates) -> HashSet<OutPoint> + '_ { |
| 82 | + |input_candidates| { |
| 83 | + let mut must_select = HashSet::new(); |
| 84 | + |
| 85 | + for original_tx in self.txs.values() { |
| 86 | + let mut largest_value = Amount::ZERO; |
| 87 | + let mut largest_value_not_canonical = Amount::ZERO; |
| 88 | + let mut largest_spend = Option::<OutPoint>::None; |
| 89 | + let original_tx_spends = original_tx.input.iter().map(|txin| txin.previous_output); |
| 90 | + for spend in original_tx_spends { |
| 91 | + // If this spends from another original tx , we do not consider it as replacing |
| 92 | + // the parent will replace this one. |
| 93 | + if self.txs.contains_key(&spend.txid) { |
| 94 | + continue; |
| 95 | + } |
| 96 | + let txout = self.prev_txouts.get(&spend).expect("must have prev txout"); |
| 97 | + |
| 98 | + // not canonical |
| 99 | + if !input_candidates.contains(spend) { |
| 100 | + if largest_value == Amount::ZERO |
| 101 | + && txout.value > largest_value_not_canonical |
| 102 | + { |
| 103 | + largest_value_not_canonical = txout.value; |
| 104 | + largest_spend = Some(spend); |
| 105 | + } |
| 106 | + continue; |
| 107 | + } |
| 108 | + |
| 109 | + if txout.value > largest_value { |
| 110 | + largest_value = txout.value; |
| 111 | + largest_spend = Some(spend); |
| 112 | + } |
| 113 | + } |
| 114 | + let largest_spend = largest_spend.expect("tx must have atleast one input"); |
| 115 | + must_select.insert(largest_spend); |
| 116 | + } |
| 117 | + |
| 118 | + must_select |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + fn _fee(&self, tx: &'t Transaction) -> Amount { |
| 123 | + let output_sum: Amount = tx.output.iter().map(|txout| txout.value).sum(); |
| 124 | + let input_sum: Amount = tx |
| 125 | + .input |
| 126 | + .iter() |
| 127 | + .map(|txin| { |
| 128 | + self.prev_txouts |
| 129 | + .get(&txin.previous_output) |
| 130 | + .expect("prev output must exist") |
| 131 | + .value |
| 132 | + }) |
| 133 | + .sum(); |
| 134 | + input_sum - output_sum |
| 135 | + } |
| 136 | + |
| 137 | + /// Coin selector RBF parameters. |
| 138 | + pub fn selector_rbf_params(&self) -> RbfParams { |
| 139 | + RbfParams::new(self.txs.values().map(|tx| (*tx, self._fee(tx)))) |
| 140 | + } |
| 141 | +} |
0 commit comments