bitcoin_rs_mempool/
rbf.rs1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use bitcoin::Transaction;
5use thiserror::Error;
6
7use crate::pool::tx_fee_rate;
8use crate::{EntryId, Mempool, MempoolEntry, MempoolError};
9
10#[derive(Clone, Debug)]
12pub struct ReplacementCandidate {
13 pub tx: Arc<Transaction>,
15 pub vsize: u32,
17 pub fee: u64,
19 pub min_relay_fee_rate: u64,
21}
22
23impl ReplacementCandidate {
24 #[must_use]
26 pub const fn new(tx: Arc<Transaction>, vsize: u32, fee: u64, min_relay_fee_rate: u64) -> Self {
27 Self {
28 tx,
29 vsize,
30 fee,
31 min_relay_fee_rate,
32 }
33 }
34
35 #[must_use]
37 pub fn fee_rate(&self) -> u64 {
38 tx_fee_rate(self.fee, self.vsize)
39 }
40}
41
42#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct ReplacementPlan {
45 pub evicted: Vec<EntryId>,
47}
48
49#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
51pub enum RbfError {
52 #[error("BIP125 rule 1: original transactions do not opt in")]
54 Rule1NoOptIn,
55 #[error("BIP125 rule 2: replacement adds a new unconfirmed input")]
57 Rule2NewUnconfirmedInput,
58 #[error("BIP125 rule 3: replacement fee does not pay evicted fees")]
60 Rule3InsufficientAbsoluteFee,
61 #[error("BIP125 rule 4: replacement does not pay incremental relay fee")]
63 Rule4InsufficientIncrementalFee,
64 #[error("BIP125 rule 5: replacement evicts too many transactions")]
66 Rule5TooManyEvictions,
67 #[error("BIP125 rule 6: replacement fee rate is not higher than originals")]
69 Rule6InsufficientFeeRate,
70 #[error(transparent)]
72 Mempool(#[from] MempoolError),
73}
74
75impl Mempool {
76 pub fn check_replacement(
78 &self,
79 candidate: &ReplacementCandidate,
80 ) -> Result<ReplacementPlan, RbfError> {
81 let direct_conflicts = self.conflicts_for(&candidate.tx);
82 if direct_conflicts.is_empty() {
83 return Ok(ReplacementPlan {
84 evicted: Vec::new(),
85 });
86 }
87
88 if !direct_conflicts
89 .iter()
90 .any(|id| self.signals_rbf_including_ancestors(*id))
91 {
92 return Err(RbfError::Rule1NoOptIn);
93 }
94
95 let original_spends = direct_conflicts
96 .iter()
97 .filter_map(|id| self.entry(*id))
98 .flat_map(|entry| entry.tx.input.iter().map(|input| input.previous_output))
99 .collect::<Vec<_>>();
100 for input in &candidate.tx.input {
101 if self.is_unconfirmed_outpoint(input.previous_output)
102 && !original_spends.contains(&input.previous_output)
103 {
104 return Err(RbfError::Rule2NewUnconfirmedInput);
105 }
106 }
107
108 let evicted = self.conflicts_with_descendants(&candidate.tx);
109 let evicted_fee = evicted.iter().fold(0_u64, |total, id| {
110 total.saturating_add(self.entry(*id).map_or(0, |entry| entry.fee))
111 });
112 if candidate.fee < evicted_fee {
113 return Err(RbfError::Rule3InsufficientAbsoluteFee);
114 }
115
116 let incremental_fee =
117 u64::from(candidate.vsize).saturating_mul(candidate.min_relay_fee_rate) / 1_000;
118 if candidate.fee.saturating_sub(evicted_fee) < incremental_fee {
119 return Err(RbfError::Rule4InsufficientIncrementalFee);
120 }
121
122 let eviction_count = u32::try_from(evicted.len()).unwrap_or(u32::MAX);
123 if eviction_count > self.limits.max_replacement_evictions {
124 return Err(RbfError::Rule5TooManyEvictions);
125 }
126
127 let candidate_fee_rate = candidate.fee_rate();
128 if direct_conflicts.iter().any(|id| {
129 self.entry(*id)
130 .is_some_and(|entry| candidate_fee_rate <= entry.fee_rate)
131 }) {
132 return Err(RbfError::Rule6InsufficientFeeRate);
133 }
134
135 Ok(ReplacementPlan { evicted })
136 }
137
138 pub fn replace_transaction(
140 &mut self,
141 candidate: ReplacementCandidate,
142 time: u64,
143 height: u32,
144 ) -> Result<EntryId, RbfError> {
145 let plan = self.check_replacement(&candidate)?;
146 for id in plan.evicted {
147 let _ = self.remove_entry_and_descendants(id);
148 }
149 let entry = MempoolEntry::new(candidate.tx, candidate.vsize, candidate.fee, time, height);
150 self.insert_entry(entry).map_err(RbfError::from)
151 }
152}