1use abtc_domain::consensus::rules;
17use abtc_domain::crypto::signing::TransactionSignatureChecker;
18use abtc_domain::policy::limits::MempoolLimits;
19use abtc_domain::primitives::{Amount, Sequence, Transaction};
20use abtc_domain::script::{verify_script_with_witness, ScriptFlags};
21use abtc_ports::{ChainStateStore, MempoolPort, UtxoEntry};
22use std::sync::Arc;
23
24const LOCKTIME_THRESHOLD: u32 = 500_000_000;
27
28#[derive(Debug)]
30pub enum AcceptError {
31 ConsensusViolation(String),
33 MissingInput(String),
35 InsufficientFee { fee: i64, required: i64 },
37 PolicyViolation(String),
39 ScriptFailure(String),
41 MempoolRejection(String),
43}
44
45impl std::fmt::Display for AcceptError {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 AcceptError::ConsensusViolation(e) => write!(f, "consensus: {}", e),
49 AcceptError::MissingInput(e) => write!(f, "missing input: {}", e),
50 AcceptError::InsufficientFee { fee, required } => {
51 write!(f, "insufficient fee: {} < {}", fee, required)
52 }
53 AcceptError::PolicyViolation(e) => write!(f, "policy: {}", e),
54 AcceptError::ScriptFailure(e) => write!(f, "script: {}", e),
55 AcceptError::MempoolRejection(e) => write!(f, "mempool: {}", e),
56 }
57 }
58}
59
60impl std::error::Error for AcceptError {}
61
62#[derive(Debug, Clone)]
64pub struct AcceptResult {
65 pub txid: abtc_domain::primitives::Txid,
67 pub fee: Amount,
69 pub vsize: u32,
71 pub fee_rate: f64,
73}
74
75pub struct MempoolAcceptor {
80 chain_state: Arc<dyn ChainStateStore>,
81 mempool: Arc<dyn MempoolPort>,
82 _limits: MempoolLimits,
84 verify_scripts: bool,
86}
87
88impl MempoolAcceptor {
89 pub fn new(chain_state: Arc<dyn ChainStateStore>, mempool: Arc<dyn MempoolPort>) -> Self {
91 MempoolAcceptor {
92 chain_state,
93 mempool,
94 _limits: MempoolLimits::default(),
95 verify_scripts: true,
96 }
97 }
98
99 pub fn with_limits(
101 chain_state: Arc<dyn ChainStateStore>,
102 mempool: Arc<dyn MempoolPort>,
103 limits: MempoolLimits,
104 ) -> Self {
105 MempoolAcceptor {
106 chain_state,
107 mempool,
108 _limits: limits,
109 verify_scripts: true,
110 }
111 }
112
113 pub fn set_verify_scripts(&mut self, verify: bool) {
115 self.verify_scripts = verify;
116 }
117
118 pub async fn accept_transaction(&self, tx: &Transaction) -> Result<AcceptResult, AcceptError> {
123 let txid = tx.txid();
124
125 rules::check_transaction(tx)
127 .map_err(|e| AcceptError::ConsensusViolation(format!("{}", e)))?;
128
129 if tx.is_coinbase() {
131 return Err(AcceptError::ConsensusViolation(
132 "coinbase transactions cannot enter the mempool".into(),
133 ));
134 }
135
136 let mut total_input_value: i64 = 0;
138 let mut input_utxos = Vec::with_capacity(tx.inputs.len());
139
140 for input in &tx.inputs {
141 let utxo = self
142 .chain_state
143 .get_utxo(&input.previous_output.txid, input.previous_output.vout)
144 .await
145 .map_err(|e| AcceptError::MissingInput(e.to_string()))?
146 .ok_or_else(|| {
147 AcceptError::MissingInput(format!(
148 "{}:{}",
149 input.previous_output.txid, input.previous_output.vout
150 ))
151 })?;
152
153 total_input_value += utxo.output.value.as_sat();
154 input_utxos.push(utxo);
155 }
156
157 let total_output_value = tx.total_output_value().as_sat();
158 if total_input_value < total_output_value {
159 return Err(AcceptError::ConsensusViolation(format!(
160 "outputs ({}) exceed inputs ({})",
161 total_output_value, total_input_value
162 )));
163 }
164
165 let fee = total_input_value - total_output_value;
166
167 if tx.lock_time > 0 {
172 let all_final = tx.inputs.iter().all(|inp| inp.sequence == Sequence::FINAL);
173 if all_final {
174 return Err(AcceptError::ConsensusViolation(
175 "non-zero lock_time but all inputs are final".into(),
176 ));
177 }
178
179 let (_tip_hash, tip_height) = self
180 .chain_state
181 .get_best_chain_tip()
182 .await
183 .map_err(|e| AcceptError::ConsensusViolation(e.to_string()))?;
184
185 if tx.lock_time < LOCKTIME_THRESHOLD {
186 let next_height = tip_height + 1;
188 if tx.lock_time > next_height {
189 return Err(AcceptError::PolicyViolation(format!(
190 "locktime {} exceeds next block height {}",
191 tx.lock_time, next_height
192 )));
193 }
194 }
195 }
198
199 if tx.version >= 2 {
205 let (_tip_hash, tip_height) = self
206 .chain_state
207 .get_best_chain_tip()
208 .await
209 .map_err(|e| AcceptError::ConsensusViolation(e.to_string()))?;
210
211 Self::check_sequence_locks(tx, &input_utxos, tip_height)?;
212 }
213
214 let vsize = Self::estimate_vsize(tx);
216 let fee_rate = fee as f64 / vsize.max(1) as f64;
217
218 let output_values: Vec<i64> = tx.outputs.iter().map(|o| o.value.as_sat()).collect();
220 let tx_weight = vsize * 4; MempoolLimits::check_standard_tx(tx_weight, Amount::from_sat(fee), vsize, &output_values)
222 .map_err(|e| AcceptError::PolicyViolation(format!("{}", e)))?;
223
224 if self.verify_scripts {
226 let script_flags = ScriptFlags::standard();
227
228 for (input_idx, input) in tx.inputs.iter().enumerate() {
229 let utxo = &input_utxos[input_idx];
230 let script_pubkey = &utxo.output.script_pubkey;
231 let spent_amount = utxo.output.value;
232
233 let checker =
234 if script_pubkey.is_witness_program() || is_p2sh_witness(tx, input_idx) {
235 TransactionSignatureChecker::new_witness_v0(tx, input_idx, spent_amount)
236 } else {
237 TransactionSignatureChecker::new(tx, input_idx, spent_amount)
238 };
239
240 verify_script_with_witness(
241 &input.script_sig,
242 script_pubkey,
243 &input.witness,
244 script_flags,
245 &checker,
246 )
247 .map_err(|e| {
248 AcceptError::ScriptFailure(format!(
249 "input {} of tx {}: {:?}",
250 input_idx, txid, e
251 ))
252 })?;
253 }
254 }
255
256 self.mempool
258 .add_transaction(tx)
259 .await
260 .map_err(|e| AcceptError::MempoolRejection(e.to_string()))?;
261
262 tracing::info!(
263 "Accepted tx {} to mempool (fee={}, vsize={}, rate={:.1} sat/vB)",
264 txid,
265 fee,
266 vsize,
267 fee_rate
268 );
269
270 Ok(AcceptResult {
271 txid,
272 fee: Amount::from_sat(fee),
273 vsize,
274 fee_rate,
275 })
276 }
277
278 fn check_sequence_locks(
286 tx: &Transaction,
287 input_utxos: &[UtxoEntry],
288 tip_height: u32,
289 ) -> Result<(), AcceptError> {
290 for (idx, input) in tx.inputs.iter().enumerate() {
291 let seq = input.sequence;
292
293 if seq & Sequence::LOCKTIME_DISABLE_FLAG != 0 || seq == Sequence::FINAL {
295 continue;
296 }
297
298 let utxo_height = input_utxos[idx].height;
299
300 if seq & Sequence::LOCKTIME_TYPE_FLAG == 0 {
301 let required_blocks = seq & Sequence::LOCKTIME_MASK;
303 let depth = tip_height.saturating_sub(utxo_height);
304 if depth < required_blocks {
305 return Err(AcceptError::PolicyViolation(format!(
306 "input {} requires {} blocks of depth but UTXO only has {} (BIP68)",
307 idx, required_blocks, depth
308 )));
309 }
310 }
311 }
314 Ok(())
315 }
316
317 fn estimate_vsize(tx: &Transaction) -> u32 {
321 let mut base_size = 10u32; let mut witness_size = 0u32;
323
324 for input in &tx.inputs {
325 base_size += 41 + input.script_sig.len() as u32; if !input.witness.is_empty() {
327 witness_size += 2; for item in input.witness.stack() {
329 witness_size += 1 + item.len() as u32; }
331 }
332 }
333
334 for output in &tx.outputs {
335 base_size += 9 + output.script_pubkey.len() as u32; }
337
338 if witness_size > 0 {
339 witness_size += 2; }
341
342 let weight = base_size * 4 + witness_size;
343 weight.div_ceil(4)
344 }
345}
346
347fn is_p2sh_witness(tx: &Transaction, input_idx: usize) -> bool {
349 let script_sig = &tx.inputs[input_idx].script_sig;
350 let bytes = script_sig.as_bytes();
351
352 if bytes.is_empty() {
353 return false;
354 }
355
356 #[allow(clippy::if_same_then_else)]
359 let inner = if bytes.len() == 23 && bytes[0] == 0x16 {
360 &bytes[1..]
361 } else if bytes.len() == 35 && bytes[0] == 0x22 {
362 &bytes[1..]
363 } else {
364 return false;
365 };
366
367 if inner.len() >= 2 {
368 let version_byte = inner[0];
369 let push_len = inner[1] as usize;
370 let is_valid_version =
371 version_byte == 0x00 || (0x51..=0x60).contains(&version_byte);
372 let is_valid_program = (push_len == 20 || push_len == 32) && inner.len() == 2 + push_len;
373 return is_valid_version && is_valid_program;
374 }
375
376 false
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use abtc_domain::primitives::{OutPoint, TxIn, TxOut, Txid};
383 use abtc_domain::Script;
384 use abtc_ports::{MempoolEntry, MempoolInfo, UtxoEntry};
385 use async_trait::async_trait;
386 use std::collections::HashMap;
387 use tokio::sync::RwLock;
388
389 struct MockChainState {
392 utxos: RwLock<HashMap<(Txid, u32), UtxoEntry>>,
393 }
394
395 impl MockChainState {
396 fn new() -> Self {
397 MockChainState {
398 utxos: RwLock::new(HashMap::new()),
399 }
400 }
401
402 async fn add_utxo(&self, txid: Txid, vout: u32, entry: UtxoEntry) {
403 self.utxos.write().await.insert((txid, vout), entry);
404 }
405 }
406
407 #[async_trait]
408 impl ChainStateStore for MockChainState {
409 async fn get_utxo(
410 &self,
411 txid: &Txid,
412 vout: u32,
413 ) -> Result<Option<UtxoEntry>, Box<dyn std::error::Error + Send + Sync>> {
414 Ok(self.utxos.read().await.get(&(*txid, vout)).cloned())
415 }
416
417 async fn has_utxo(
418 &self,
419 txid: &Txid,
420 vout: u32,
421 ) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
422 Ok(self.utxos.read().await.contains_key(&(*txid, vout)))
423 }
424
425 async fn write_utxo_set(
426 &self,
427 _adds: Vec<(Txid, u32, UtxoEntry)>,
428 _removes: Vec<(Txid, u32)>,
429 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
430 Ok(())
431 }
432
433 async fn get_best_chain_tip(
434 &self,
435 ) -> Result<
436 (abtc_domain::primitives::BlockHash, u32),
437 Box<dyn std::error::Error + Send + Sync>,
438 > {
439 Ok((abtc_domain::primitives::BlockHash::zero(), 0))
440 }
441
442 async fn write_chain_tip(
443 &self,
444 _hash: abtc_domain::primitives::BlockHash,
445 _height: u32,
446 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
447 Ok(())
448 }
449 async fn get_utxo_set_info(
450 &self,
451 ) -> Result<abtc_ports::UtxoSetInfo, Box<dyn std::error::Error + Send + Sync>> {
452 Ok(abtc_ports::UtxoSetInfo {
453 txout_count: 0,
454 total_amount: abtc_domain::primitives::Amount::from_sat(0),
455 best_block: abtc_domain::primitives::BlockHash::zero(),
456 height: 0,
457 })
458 }
459 }
460
461 struct MockMempool {
464 txs: RwLock<HashMap<Txid, Transaction>>,
465 }
466
467 impl MockMempool {
468 fn new() -> Self {
469 MockMempool {
470 txs: RwLock::new(HashMap::new()),
471 }
472 }
473 }
474
475 #[async_trait]
476 impl MempoolPort for MockMempool {
477 async fn add_transaction(
478 &self,
479 tx: &Transaction,
480 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
481 let txid = tx.txid();
482 let mut txs = self.txs.write().await;
483 if txs.contains_key(&txid) {
484 return Err("already in mempool".into());
485 }
486 txs.insert(txid, tx.clone());
487 Ok(())
488 }
489
490 async fn remove_transaction(
491 &self,
492 txid: &Txid,
493 _recursive: bool,
494 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
495 self.txs.write().await.remove(txid);
496 Ok(())
497 }
498
499 async fn get_transaction(
500 &self,
501 txid: &Txid,
502 ) -> Result<Option<MempoolEntry>, Box<dyn std::error::Error + Send + Sync>> {
503 let txs = self.txs.read().await;
504 Ok(txs.get(txid).map(|tx| MempoolEntry {
505 tx: tx.clone(),
506 fee: Amount::from_sat(0),
507 size: 100,
508 time: 0,
509 height: 0,
510 descendant_count: 0,
511 descendant_size: 0,
512 ancestor_count: 0,
513 ancestor_size: 0,
514 }))
515 }
516
517 async fn get_all_transactions(
518 &self,
519 ) -> Result<Vec<MempoolEntry>, Box<dyn std::error::Error + Send + Sync>> {
520 Ok(vec![])
521 }
522
523 async fn get_transaction_count(
524 &self,
525 ) -> Result<u32, Box<dyn std::error::Error + Send + Sync>> {
526 Ok(self.txs.read().await.len() as u32)
527 }
528
529 async fn estimate_fee(
530 &self,
531 _target_blocks: u32,
532 ) -> Result<f64, Box<dyn std::error::Error + Send + Sync>> {
533 Ok(1.0)
534 }
535
536 async fn get_mempool_info(
537 &self,
538 ) -> Result<MempoolInfo, Box<dyn std::error::Error + Send + Sync>> {
539 Ok(MempoolInfo {
540 size: 0,
541 bytes: 0,
542 usage: 0,
543 max_mempool: 300_000_000,
544 min_relay_fee: 0.00001,
545 })
546 }
547
548 async fn clear(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
549 self.txs.write().await.clear();
550 Ok(())
551 }
552 }
553
554 fn make_funding_utxo(value: i64) -> UtxoEntry {
557 UtxoEntry {
558 output: TxOut::new(Amount::from_sat(value), Script::new()),
559 height: 1,
560 is_coinbase: false,
561 }
562 }
563
564 #[tokio::test]
567 async fn test_accept_valid_transaction() {
568 let chain_state = Arc::new(MockChainState::new());
569 let mempool = Arc::new(MockMempool::new());
570
571 let funding_txid =
573 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x01; 32]));
574 chain_state
575 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
576 .await;
577
578 let mut acceptor = MempoolAcceptor::new(chain_state, mempool.clone());
579 acceptor.set_verify_scripts(false); let tx = Transaction::v1(
582 vec![TxIn::final_input(
583 OutPoint::new(funding_txid, 0),
584 Script::new(),
585 )],
586 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
587 0,
588 );
589
590 let result = acceptor.accept_transaction(&tx).await;
591 assert!(result.is_ok());
592
593 let accept = result.unwrap();
594 assert_eq!(accept.fee.as_sat(), 10_000);
595 assert!(accept.fee_rate > 0.0);
596
597 let count = mempool.get_transaction_count().await.unwrap();
599 assert_eq!(count, 1);
600 }
601
602 #[tokio::test]
603 async fn test_reject_missing_input() {
604 let chain_state = Arc::new(MockChainState::new());
605 let mempool = Arc::new(MockMempool::new());
606
607 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
608 acceptor.set_verify_scripts(false);
609
610 let tx = Transaction::v1(
611 vec![TxIn::final_input(
612 OutPoint::new(Txid::zero(), 0),
613 Script::new(),
614 )],
615 vec![TxOut::new(Amount::from_sat(1000), Script::new())],
616 0,
617 );
618
619 let result = acceptor.accept_transaction(&tx).await;
620 assert!(result.is_err());
621 assert!(matches!(result.unwrap_err(), AcceptError::MissingInput(_)));
622 }
623
624 #[tokio::test]
625 async fn test_reject_coinbase() {
626 let chain_state = Arc::new(MockChainState::new());
627 let mempool = Arc::new(MockMempool::new());
628
629 let acceptor = MempoolAcceptor::new(chain_state, mempool);
630
631 let tx = Transaction::coinbase(
632 0,
633 Script::from_bytes(vec![0x01, 0x00]),
634 vec![TxOut::new(Amount::from_sat(5_000_000_000), Script::new())],
635 );
636
637 let result = acceptor.accept_transaction(&tx).await;
638 assert!(result.is_err());
639 assert!(matches!(
640 result.unwrap_err(),
641 AcceptError::ConsensusViolation(_)
642 ));
643 }
644
645 #[tokio::test]
646 async fn test_reject_outputs_exceed_inputs() {
647 let chain_state = Arc::new(MockChainState::new());
648 let mempool = Arc::new(MockMempool::new());
649
650 let funding_txid =
651 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x02; 32]));
652 chain_state
653 .add_utxo(funding_txid, 0, make_funding_utxo(1_000))
654 .await;
655
656 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
657 acceptor.set_verify_scripts(false);
658
659 let tx = Transaction::v1(
660 vec![TxIn::final_input(
661 OutPoint::new(funding_txid, 0),
662 Script::new(),
663 )],
664 vec![TxOut::new(Amount::from_sat(2_000), Script::new())],
665 0,
666 );
667
668 let result = acceptor.accept_transaction(&tx).await;
669 assert!(result.is_err());
670 assert!(matches!(
671 result.unwrap_err(),
672 AcceptError::ConsensusViolation(_)
673 ));
674 }
675
676 #[tokio::test]
677 async fn test_reject_dust_output() {
678 let chain_state = Arc::new(MockChainState::new());
679 let mempool = Arc::new(MockMempool::new());
680
681 let funding_txid =
682 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x03; 32]));
683 chain_state
684 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
685 .await;
686
687 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
688 acceptor.set_verify_scripts(false);
689
690 let tx = Transaction::v1(
692 vec![TxIn::final_input(
693 OutPoint::new(funding_txid, 0),
694 Script::new(),
695 )],
696 vec![TxOut::new(Amount::from_sat(100), Script::new())],
697 0,
698 );
699
700 let result = acceptor.accept_transaction(&tx).await;
701 assert!(result.is_err());
702 assert!(matches!(
703 result.unwrap_err(),
704 AcceptError::PolicyViolation(_)
705 ));
706 }
707
708 #[tokio::test]
709 async fn test_fee_calculation() {
710 let chain_state = Arc::new(MockChainState::new());
711 let mempool = Arc::new(MockMempool::new());
712
713 let funding_txid =
714 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x04; 32]));
715 chain_state
716 .add_utxo(funding_txid, 0, make_funding_utxo(500_000))
717 .await;
718
719 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
720 acceptor.set_verify_scripts(false);
721
722 let tx = Transaction::v1(
723 vec![TxIn::final_input(
724 OutPoint::new(funding_txid, 0),
725 Script::new(),
726 )],
727 vec![TxOut::new(Amount::from_sat(450_000), Script::new())],
728 0,
729 );
730
731 let result = acceptor.accept_transaction(&tx).await.unwrap();
732 assert_eq!(result.fee.as_sat(), 50_000);
733 assert!(result.vsize > 0);
734 }
735
736 #[tokio::test]
737 async fn test_vsize_estimation() {
738 let tx = Transaction::v1(
740 vec![TxIn::final_input(
741 OutPoint::new(Txid::zero(), 0),
742 Script::new(),
743 )],
744 vec![TxOut::new(Amount::from_sat(1000), Script::new())],
745 0,
746 );
747 let vsize = MempoolAcceptor::estimate_vsize(&tx);
748 assert!(vsize > 0);
749 assert!(vsize >= 50 && vsize <= 100, "vsize was {}", vsize);
751 }
752
753 #[tokio::test]
754 async fn test_duplicate_rejection() {
755 let chain_state = Arc::new(MockChainState::new());
756 let mempool = Arc::new(MockMempool::new());
757
758 let funding_txid =
759 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x05; 32]));
760 chain_state
761 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
762 .await;
763
764 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
765 acceptor.set_verify_scripts(false);
766
767 let tx = Transaction::v1(
768 vec![TxIn::final_input(
769 OutPoint::new(funding_txid, 0),
770 Script::new(),
771 )],
772 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
773 0,
774 );
775
776 assert!(acceptor.accept_transaction(&tx).await.is_ok());
778
779 let result = acceptor.accept_transaction(&tx).await;
781 assert!(result.is_err());
782 assert!(matches!(
783 result.unwrap_err(),
784 AcceptError::MempoolRejection(_)
785 ));
786 }
787
788 #[tokio::test]
796 async fn regression_locktime_future_block_rejected() {
797 let chain_state = Arc::new(MockChainState::new());
798 let mempool = Arc::new(MockMempool::new());
799
800 let funding_txid =
801 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x10; 32]));
802 chain_state
803 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
804 .await;
805
806 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
807 acceptor.set_verify_scripts(false);
808
809 let tx = Transaction::new(
811 1,
812 vec![TxIn::new(
813 OutPoint::new(funding_txid, 0),
814 Script::new(),
815 Sequence::MAX_NONFINAL, )],
817 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
818 1000, );
820
821 let result = acceptor.accept_transaction(&tx).await;
822 assert!(result.is_err());
823 let err = format!("{}", result.unwrap_err());
824 assert!(err.contains("locktime"), "error was: {}", err);
825 }
826
827 #[tokio::test]
828 async fn regression_locktime_all_final_rejected() {
829 let chain_state = Arc::new(MockChainState::new());
830 let mempool = Arc::new(MockMempool::new());
831
832 let funding_txid =
833 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x11; 32]));
834 chain_state
835 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
836 .await;
837
838 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
839 acceptor.set_verify_scripts(false);
840
841 let tx = Transaction::new(
843 1,
844 vec![TxIn::final_input(
845 OutPoint::new(funding_txid, 0),
846 Script::new(),
847 )],
848 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
849 100, );
851
852 let result = acceptor.accept_transaction(&tx).await;
853 assert!(result.is_err());
854 let err = format!("{}", result.unwrap_err());
855 assert!(err.contains("all inputs are final"), "error was: {}", err);
856 }
857
858 #[tokio::test]
859 async fn regression_locktime_zero_accepted() {
860 let chain_state = Arc::new(MockChainState::new());
861 let mempool = Arc::new(MockMempool::new());
862
863 let funding_txid =
864 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x12; 32]));
865 chain_state
866 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
867 .await;
868
869 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
870 acceptor.set_verify_scripts(false);
871
872 let tx = Transaction::v1(
874 vec![TxIn::final_input(
875 OutPoint::new(funding_txid, 0),
876 Script::new(),
877 )],
878 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
879 0,
880 );
881
882 assert!(acceptor.accept_transaction(&tx).await.is_ok());
883 }
884
885 #[tokio::test]
886 async fn regression_bip68_relative_lock_rejected() {
887 let chain_state = Arc::new(MockChainState::new());
888 let mempool = Arc::new(MockMempool::new());
889
890 let funding_txid =
891 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x13; 32]));
892 chain_state
894 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
895 .await;
896
897 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
898 acceptor.set_verify_scripts(false);
899
900 let relative_lock_sequence = 10u32; let tx = Transaction::new(
904 2,
905 vec![TxIn::new(
906 OutPoint::new(funding_txid, 0),
907 Script::new(),
908 relative_lock_sequence,
909 )],
910 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
911 0,
912 );
913
914 let result = acceptor.accept_transaction(&tx).await;
915 assert!(result.is_err());
916 let err = format!("{}", result.unwrap_err());
917 assert!(err.contains("BIP68"), "error was: {}", err);
918 }
919
920 #[tokio::test]
921 async fn regression_bip68_disabled_flag_accepted() {
922 let chain_state = Arc::new(MockChainState::new());
923 let mempool = Arc::new(MockMempool::new());
924
925 let funding_txid =
926 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x14; 32]));
927 chain_state
928 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
929 .await;
930
931 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
932 acceptor.set_verify_scripts(false);
933
934 let seq_disabled = Sequence::LOCKTIME_DISABLE_FLAG | 10;
936 let tx = Transaction::new(
937 2,
938 vec![TxIn::new(
939 OutPoint::new(funding_txid, 0),
940 Script::new(),
941 seq_disabled,
942 )],
943 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
944 0,
945 );
946
947 assert!(acceptor.accept_transaction(&tx).await.is_ok());
948 }
949
950 #[tokio::test]
951 async fn regression_bip68_v1_tx_skips_sequence_check() {
952 let chain_state = Arc::new(MockChainState::new());
953 let mempool = Arc::new(MockMempool::new());
954
955 let funding_txid =
956 Txid::from_hash(abtc_domain::primitives::Hash256::from_bytes([0x15; 32]));
957 chain_state
958 .add_utxo(funding_txid, 0, make_funding_utxo(100_000))
959 .await;
960
961 let mut acceptor = MempoolAcceptor::new(chain_state, mempool);
962 acceptor.set_verify_scripts(false);
963
964 let tx = Transaction::new(
966 1,
967 vec![TxIn::new(
968 OutPoint::new(funding_txid, 0),
969 Script::new(),
970 10, )],
972 vec![TxOut::new(Amount::from_sat(90_000), Script::new())],
973 0,
974 );
975
976 assert!(acceptor.accept_transaction(&tx).await.is_ok());
977 }
978}