subcoin_primitives/
tx_pool.rs1use bitcoin::{Transaction, Txid};
4use std::sync::Arc;
5
6#[derive(Debug, Clone)]
8pub enum TxValidationResult {
9 Accepted {
11 txid: Txid,
12 fee_rate: u64,
14 },
15 Rejected { txid: Txid, reason: RejectionReason },
17}
18
19#[derive(Debug, Clone)]
21pub enum RejectionReason {
22 Soft(SoftRejection),
24 Hard(HardRejection),
26}
27
28impl RejectionReason {
29 pub fn should_penalize_peer(&self) -> bool {
31 matches!(self, Self::Hard(_))
32 }
33}
34
35#[derive(Debug, Clone)]
37pub enum SoftRejection {
38 AlreadyInMempool,
40 MissingInputs {
42 parents: Vec<Txid>,
43 },
44 FeeTooLow {
46 min_kvb: u64,
47 actual_kvb: u64,
48 },
49 MempoolFull,
51 TooManyAncestors(usize),
53 TooManyDescendants(usize),
54 TxConflict(String),
56 NoConflictToReplace,
58 TxNotReplaceable,
59 TooManyReplacements(usize),
60 NewUnconfirmedInput,
61 InsufficientFee(String),
62 PackageTooLarge(usize, usize),
64 PackageSizeTooLarge(u64),
65 PackageCyclicDependencies,
66 PackageFeeTooLow(String),
67 PackageTxValidationFailed(Txid, String),
68 PackageRelayDisabled,
69}
70
71#[derive(Debug, Clone)]
73pub enum HardRejection {
74 Coinbase,
76 NotStandard(String),
78 TxVersionNotStandard,
79 TxSizeTooSmall,
80 NonFinal,
82 NonBIP68Final,
83 TooManySigops(i64),
85 NegativeFee,
87 FeeOverflow,
88 InvalidFeeRate(String),
89 AncestorSizeTooLarge(i64),
91 DescendantSizeTooLarge(i64),
92 ScriptValidationFailed(String),
94 TxError(String),
96 RuntimeApi(String),
97}
98
99#[derive(Debug, Clone)]
101pub struct TxPoolInfo {
102 pub size: usize,
104 pub bytes: u64,
106 pub usage: u64,
108 pub min_fee_rate: u64,
110}
111
112pub trait TxPool: Send + Sync + 'static {
120 fn validate_transaction(&self, tx: Transaction) -> TxValidationResult;
126
127 fn contains(&self, txid: &Txid) -> bool;
129
130 fn get(&self, txid: &Txid) -> Option<Arc<Transaction>>;
132
133 fn pending_broadcast(&self) -> Vec<(Txid, u64)>;
136
137 fn mark_broadcast(&self, txids: &[Txid]);
139
140 fn iter_txids(&self) -> Box<dyn Iterator<Item = (Txid, u64)> + Send>;
143
144 fn info(&self) -> TxPoolInfo;
146}
147
148#[derive(Debug, Default, Clone)]
153pub struct NoTxPool;
154
155impl TxPool for NoTxPool {
156 fn validate_transaction(&self, tx: Transaction) -> TxValidationResult {
157 TxValidationResult::Rejected {
158 txid: tx.compute_txid(),
159 reason: RejectionReason::Soft(SoftRejection::PackageRelayDisabled),
160 }
161 }
162
163 fn contains(&self, _txid: &Txid) -> bool {
164 false
165 }
166
167 fn get(&self, _txid: &Txid) -> Option<Arc<Transaction>> {
168 None
169 }
170
171 fn pending_broadcast(&self) -> Vec<(Txid, u64)> {
172 Vec::new()
173 }
174
175 fn mark_broadcast(&self, _txids: &[Txid]) {}
176
177 fn iter_txids(&self) -> Box<dyn Iterator<Item = (Txid, u64)> + Send> {
178 Box::new(std::iter::empty())
179 }
180
181 fn info(&self) -> TxPoolInfo {
182 TxPoolInfo {
183 size: 0,
184 bytes: 0,
185 usage: 0,
186 min_fee_rate: 0,
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use bitcoin::hashes::Hash;
195
196 #[test]
197 fn test_noop_pool() {
198 let pool = NoTxPool;
199 let tx = Transaction {
200 version: bitcoin::transaction::Version::TWO,
201 lock_time: bitcoin::absolute::LockTime::ZERO,
202 input: vec![],
203 output: vec![],
204 };
205
206 let result = pool.validate_transaction(tx);
207 assert!(matches!(
208 result,
209 TxValidationResult::Rejected {
210 reason: RejectionReason::Soft(SoftRejection::PackageRelayDisabled),
211 ..
212 }
213 ));
214 assert!(!pool.contains(&Txid::all_zeros()));
215 assert!(pool.get(&Txid::all_zeros()).is_none());
216 assert_eq!(pool.pending_broadcast().len(), 0);
217 assert_eq!(pool.info().size, 0);
218 }
219
220 #[test]
221 fn test_rejection_reason_penalize() {
222 let soft = RejectionReason::Soft(SoftRejection::PackageRelayDisabled);
223 assert!(!soft.should_penalize_peer());
224
225 let hard = RejectionReason::Hard(HardRejection::Coinbase);
226 assert!(hard.should_penalize_peer());
227 }
228
229 #[test]
230 fn test_rejection_reason_penalty() {
231 let soft = RejectionReason::Soft(SoftRejection::AlreadyInMempool);
232 assert!(!soft.should_penalize_peer());
233
234 let hard = RejectionReason::Hard(HardRejection::Coinbase);
235 assert!(hard.should_penalize_peer());
236 }
237}