bitcoin_rs_mempool/
entry.rs1use alloc::sync::Arc;
2
3use bitcoin::Transaction;
4
5pub type EntryId = u32;
7
8#[derive(Clone, Debug)]
10pub struct MempoolEntry {
11 pub tx: Arc<Transaction>,
13 pub vsize: u32,
15 pub fee: u64,
17 pub fee_rate: u64,
19 pub ancestor_size: u64,
21 pub ancestor_fee: u64,
23 pub descendant_size: u64,
25 pub descendant_fee: u64,
27 pub time: u64,
29 pub height: u32,
31}
32
33impl MempoolEntry {
34 #[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 #[must_use]
54 pub const fn ancestor_fee_rate(&self) -> u64 {
55 fee_rate(self.ancestor_fee, self.ancestor_size)
56 }
57
58 #[must_use]
60 pub const fn descendant_fee_rate(&self) -> u64 {
61 fee_rate(self.descendant_fee, self.descendant_size)
62 }
63
64 #[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}