Skip to main content

bitcoin_rs_mempool/
entry.rs

1use alloc::sync::Arc;
2
3use bitcoin::Transaction;
4
5/// Stable mempool entry identifier.
6pub type EntryId = u32;
7
8/// Transaction plus policy accounting used by mempool ordering and limits.
9#[derive(Clone, Debug)]
10pub struct MempoolEntry {
11    /// Transaction payload shared with downstream consumers.
12    pub tx: Arc<Transaction>,
13    /// Virtual transaction size in vbytes.
14    pub vsize: u32,
15    /// Transaction fee in satoshis.
16    pub fee: u64,
17    /// Fee rate in sat/vB multiplied by 1000.
18    pub fee_rate: u64,
19    /// Total virtual size of this entry and all unconfirmed ancestors.
20    pub ancestor_size: u64,
21    /// Total fee of this entry and all unconfirmed ancestors.
22    pub ancestor_fee: u64,
23    /// Total virtual size of this entry and all unconfirmed descendants.
24    pub descendant_size: u64,
25    /// Total fee of this entry and all unconfirmed descendants.
26    pub descendant_fee: u64,
27    /// Mempool acceptance time in monotonically increasing seconds.
28    pub time: u64,
29    /// Chain height at acceptance.
30    pub height: u32,
31}
32
33impl MempoolEntry {
34    /// Builds a mempool entry with self-only ancestor and descendant accounting.
35    #[must_use]
36    pub fn new(tx: Arc<Transaction>, vsize: u32, fee: u64, time: u64, height: u32) -> Self {
37        let own_size = u64::from(vsize);
38        Self {
39            tx,
40            vsize,
41            fee,
42            fee_rate: fee_rate(fee, own_size),
43            ancestor_size: own_size,
44            ancestor_fee: fee,
45            descendant_size: own_size,
46            descendant_fee: fee,
47            time,
48            height,
49        }
50    }
51
52    /// Ancestor package fee rate in sat/vB multiplied by 1000.
53    #[must_use]
54    pub const fn ancestor_fee_rate(&self) -> u64 {
55        fee_rate(self.ancestor_fee, self.ancestor_size)
56    }
57
58    /// Descendant package fee rate in sat/vB multiplied by 1000.
59    #[must_use]
60    pub const fn descendant_fee_rate(&self) -> u64 {
61        fee_rate(self.descendant_fee, self.descendant_size)
62    }
63
64    /// Returns whether this transaction signals BIP-125 replaceability
65    /// (any input has `sequence < 0xFFFF_FFFE`).
66    ///
67    /// Bitcoin Core's `bip125-replaceable` mempool entry field is derived from
68    /// this predicate. Lifted from the inline check in `Mempool::iter_replaceable_txids`.
69    #[must_use]
70    pub fn is_replaceable(&self) -> bool {
71        const RBF_FLAG_THRESHOLD: u32 = 0xFFFF_FFFE;
72        self.tx
73            .input
74            .iter()
75            .any(|input| input.sequence.0 < RBF_FLAG_THRESHOLD)
76    }
77}
78
79pub(crate) const fn fee_rate(fee: u64, vsize: u64) -> u64 {
80    if vsize == 0 {
81        return 0;
82    }
83    fee.saturating_mul(1_000) / vsize
84}
85#[cfg(test)]
86mod is_replaceable_tests {
87    use super::*;
88    use std::sync::Arc;
89
90    fn entry_with_sequence(sequence: u32) -> MempoolEntry {
91        let tx = bitcoin::Transaction {
92            version: bitcoin::transaction::Version(2),
93            lock_time: bitcoin::absolute::LockTime::ZERO,
94            input: vec![bitcoin::TxIn {
95                previous_output: bitcoin::OutPoint::default(),
96                script_sig: bitcoin::ScriptBuf::new(),
97                sequence: bitcoin::Sequence(sequence),
98                witness: bitcoin::Witness::new(),
99            }],
100            output: vec![],
101        };
102        MempoolEntry::new(Arc::new(tx), 100, 10_000, 1, 7)
103    }
104
105    #[test]
106    fn is_replaceable_true_for_rbf_signal() {
107        let entry = entry_with_sequence(0xFFFF_FFFD);
108        assert!(entry.is_replaceable());
109    }
110
111    #[test]
112    fn is_replaceable_false_for_max_sequence() {
113        let entry = entry_with_sequence(0xFFFF_FFFE);
114        assert!(!entry.is_replaceable());
115    }
116
117    #[test]
118    fn is_replaceable_false_for_disabled_sequence() {
119        let entry = entry_with_sequence(0xFFFF_FFFF);
120        assert!(!entry.is_replaceable());
121    }
122
123    #[test]
124    fn is_replaceable_false_for_no_inputs() {
125        let tx = bitcoin::Transaction {
126            version: bitcoin::transaction::Version(2),
127            lock_time: bitcoin::absolute::LockTime::ZERO,
128            input: vec![],
129            output: vec![],
130        };
131        let entry = MempoolEntry::new(Arc::new(tx), 100, 10_000, 1, 7);
132        assert!(!entry.is_replaceable());
133    }
134}