1use crate::constants::*;
4use crate::economic::calculate_fee;
5use crate::error::{ConsensusError, Result};
6use crate::script::{SigVersion, verify_script_with_context_full};
7use crate::segwit::Witness;
8use crate::transaction::{check_transaction, check_tx_inputs};
9use crate::types::*;
10#[cfg(feature = "spec-lock")]
11use blvm_spec_lock::spec_locked;
12use std::collections::{HashMap, HashSet};
13
14pub fn accept_to_memory_pool(
37 tx: &Transaction,
38 witnesses: Option<&[Witness]>,
39 utxo_set: &UtxoSet,
40 mempool: &Mempool,
41 height: Natural,
42 time_context: Option<TimeContext>,
43 network: Network,
44 prev_mtps: Option<&[i64]>,
45) -> Result<MempoolResult> {
46 if tx.inputs.is_empty() && tx.outputs.is_empty() {
50 return Ok(MempoolResult::Rejected(
51 "Transaction must have at least one input or output".to_string(),
52 ));
53 }
54 if is_coinbase(tx) {
55 return Ok(MempoolResult::Rejected(
56 "Coinbase transactions cannot be added to mempool".to_string(),
57 ));
58 }
59 if height > i64::MAX as u64 {
60 return Ok(MempoolResult::Rejected(format!(
61 "Block height {height} exceeds i64::MAX"
62 )));
63 }
64 if utxo_set.len() > u32::MAX as usize {
65 return Ok(MempoolResult::Rejected(format!(
66 "UTXO set size {} exceeds maximum",
67 utxo_set.len()
68 )));
69 }
70 if let Some(wits) = witnesses {
71 if wits.len() != tx.inputs.len() {
72 return Ok(MempoolResult::Rejected(format!(
73 "Witness count {} must match input count {}",
74 wits.len(),
75 tx.inputs.len()
76 )));
77 }
78 }
79
80 let tx_id = crate::block::calculate_tx_id(tx);
82 if tx_id == [0u8; 32] {
83 return Err(ConsensusError::TransactionValidation(
84 "Transaction ID must be non-zero".into(),
85 ));
86 }
87 if mempool.contains(&tx_id) {
88 return Ok(MempoolResult::Rejected(
89 "Transaction already in mempool".to_string(),
90 ));
91 }
92
93 if !matches!(check_transaction(tx)?, ValidationResult::Valid) {
95 return Ok(MempoolResult::Rejected(
96 "Invalid transaction structure".to_string(),
97 ));
98 }
99
100 let block_time = time_context.map(|ctx| ctx.median_time_past).unwrap_or(0);
103 if !is_final_tx(tx, height, block_time) {
104 return Ok(MempoolResult::Rejected(
105 "Transaction not final (locktime not satisfied)".to_string(),
106 ));
107 }
108 if relative_lock_unsatisfied(tx, utxo_set, height, block_time, prev_mtps, network)? {
109 return Ok(MempoolResult::Rejected(
110 "Transaction not final (relative locktime not satisfied)".to_string(),
111 ));
112 }
113
114 let (input_valid, fee) = check_tx_inputs(tx, utxo_set, height)?;
116 use crate::constants::MAX_MONEY;
117 if !(0..=MAX_MONEY).contains(&fee) {
118 return Ok(MempoolResult::Rejected(format!(
119 "Fee {fee} must be in [0, MAX_MONEY]"
120 )));
121 }
122 if !matches!(input_valid, ValidationResult::Valid) {
123 return Ok(MempoolResult::Rejected(
124 "Invalid transaction inputs".to_string(),
125 ));
126 }
127
128 if !is_coinbase(tx) {
130 let flags = calculate_script_flags(tx, witnesses, network);
131 if let Some(msg) = verify_mempool_scripts(
132 tx,
133 witnesses,
134 utxo_set,
135 height,
136 time_context,
137 flags,
138 network,
139 )? {
140 return Ok(MempoolResult::Rejected(msg));
141 }
142 }
143
144 if !check_mempool_rules(tx, witnesses, fee, mempool)? {
146 return Ok(MempoolResult::Rejected("Failed mempool rules".to_string()));
147 }
148
149 if has_conflicts(tx, mempool)? {
151 return Ok(MempoolResult::Rejected(
152 "Transaction conflicts with mempool".to_string(),
153 ));
154 }
155
156 Ok(MempoolResult::Accepted)
157}
158
159fn calculate_script_flags(
166 tx: &Transaction,
167 witnesses: Option<&[Witness]>,
168 network: Network,
169) -> u32 {
170 let has_witness = witnesses.map(|w| !w.is_empty()).unwrap_or(false);
181 const MEMPOOL_POLICY_HEIGHT: u64 = 1_000_000; crate::block::calculate_script_flags_for_block_network(
183 tx,
184 has_witness,
185 MEMPOOL_POLICY_HEIGHT,
186 network,
187 )
188}
189
190pub fn is_standard_tx_with_config(
196 tx: &crate::types::Transaction,
197 config: Option<&blvm_primitives::config::MempoolConfig>,
198) -> Result<bool> {
199 let Some(cfg) = config else {
200 return is_standard_tx(tx);
201 };
202
203 let tx_size = crate::transaction::calculate_transaction_size(tx);
205 if tx_size > MAX_TX_SIZE {
206 return Ok(false);
207 }
208 for input in tx.inputs.iter() {
209 if input.script_sig.len() > MAX_SCRIPT_SIZE {
210 return Ok(false);
211 }
212 }
213
214 let max_script = cfg.max_standard_script_size as usize;
215 let max_op_return = cfg.max_op_return_size as usize;
216 let reject_envelope = cfg.reject_unexec_if;
217 let reject_multi_op_return = cfg.reject_multiple_op_return;
218
219 let mut op_return_count = 0usize;
220 for output in tx.outputs.iter() {
221 let s = &output.script_pubkey;
222
223 if s.len() > max_script {
225 return Ok(false);
226 }
227
228 if s.first() == Some(&0x6a) {
229 op_return_count += 1;
231 if s.len().saturating_sub(1) > max_op_return {
232 return Ok(false);
233 }
234 } else if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
235 if reject_envelope {
237 return Ok(false);
238 }
239 }
240 }
241
242 if reject_multi_op_return && op_return_count > 1 {
244 return Ok(false);
245 }
246
247 Ok(true)
248}
249
250pub fn is_standard_tx(tx: &Transaction) -> Result<bool> {
258 let tx_size = calculate_transaction_size(tx);
260 if tx_size > MAX_TX_SIZE {
261 return Ok(false);
262 }
263
264 for input in tx.inputs.iter() {
266 if input.script_sig.len() > MAX_SCRIPT_SIZE {
267 return Ok(false);
268 }
269 }
270
271 for output in tx.outputs.iter() {
272 if output.script_pubkey.len() > MAX_SCRIPT_SIZE {
273 return Ok(false);
274 }
275 }
276
277 let mut op_return_count = 0usize;
279 for output in tx.outputs.iter() {
280 if !is_standard_script(&output.script_pubkey)? {
281 return Ok(false);
282 }
283
284 if output.script_pubkey.first() == Some(&0x6a) {
286 op_return_count += 1;
287 }
288
289 let s = &output.script_pubkey;
291 if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
292 return Ok(false);
293 }
294 }
295
296 if op_return_count > 1 {
298 return Ok(false);
299 }
300
301 Ok(true)
302}
303
304pub fn replacement_checks(
312 new_tx: &Transaction,
313 existing_tx: &Transaction,
314 utxo_set: &UtxoSet,
315 mempool: &Mempool,
316) -> Result<bool> {
317 replacement_checks_with_witness(new_tx, existing_tx, utxo_set, mempool, None, None)
318}
319
320pub fn replacement_checks_with_witness(
324 new_tx: &Transaction,
325 existing_tx: &Transaction,
326 utxo_set: &UtxoSet,
327 mempool: &Mempool,
328 new_witnesses: Option<&[Witness]>,
329 existing_witnesses: Option<&[Witness]>,
330) -> Result<bool> {
331 if new_tx.inputs.is_empty() && new_tx.outputs.is_empty() {
336 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
337 "New transaction must have at least one input or output"
338 .to_string()
339 .into(),
340 ));
341 }
342 if existing_tx.inputs.is_empty() && existing_tx.outputs.is_empty() {
343 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
344 "Existing transaction must have at least one input or output"
345 .to_string()
346 .into(),
347 ));
348 }
349 if is_coinbase(new_tx) {
350 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
351 "New transaction cannot be coinbase".to_string().into(),
352 ));
353 }
354 if is_coinbase(existing_tx) {
355 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
356 "Existing transaction cannot be coinbase".to_string().into(),
357 ));
358 }
359 if utxo_set.len() > u32::MAX as usize {
362 return Err(crate::error::ConsensusError::BlockValidation(
363 format!("UTXO set size {} exceeds maximum", utxo_set.len()).into(),
364 ));
365 }
366
367 let new_fee = match calculate_fee(new_tx, utxo_set) {
370 Ok(f) => f,
371 Err(crate::error::ConsensusError::UtxoNotFound(_)) => return Ok(false),
372 Err(e) => return Err(e),
373 };
374 let existing_fee = match calculate_fee(existing_tx, utxo_set) {
375 Ok(f) => f,
376 Err(crate::error::ConsensusError::UtxoNotFound(_)) => return Ok(false),
377 Err(e) => return Err(e),
378 };
379 use crate::constants::MAX_MONEY;
380 if new_fee < 0 || existing_fee < 0 || new_fee > MAX_MONEY || existing_fee > MAX_MONEY {
381 return Ok(false);
382 }
383
384 let new_tx_size = witnessed_vsize(new_tx, new_witnesses, mempool);
385 let existing_tx_size = witnessed_vsize(existing_tx, existing_witnesses, mempool);
386 if new_tx_size == 0
387 || existing_tx_size == 0
388 || new_tx_size > MAX_TX_SIZE * 2
389 || existing_tx_size > MAX_TX_SIZE * 2
390 {
391 return Ok(false);
392 }
393
394 let new_fee_scaled = (new_fee as u128)
402 .checked_mul(existing_tx_size as u128)
403 .ok_or_else(|| {
404 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
405 })?;
406 let existing_fee_scaled = (existing_fee as u128)
407 .checked_mul(new_tx_size as u128)
408 .ok_or_else(|| {
409 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
410 })?;
411
412 if new_fee_scaled <= existing_fee_scaled {
413 return Ok(false);
414 }
415
416 let bandwidth = incremental_relay_bump(new_tx_size as u64);
418 if new_fee < existing_fee + bandwidth {
419 return Ok(false);
420 }
421
422 if !has_conflict_with_tx(new_tx, existing_tx) {
424 return Ok(false);
425 }
426
427 if creates_new_dependencies(new_tx, existing_tx, utxo_set, mempool)? {
430 return Ok(false);
431 }
432
433 Ok(true)
434}
435
436#[derive(Clone, Debug, Default, PartialEq, Eq)]
442pub struct Mempool {
443 txids: HashSet<Hash>,
444 spent_outpoints: HashSet<OutPoint>,
445 tx_spent_outpoints: HashMap<Hash, Vec<OutPoint>>,
447 total_vbytes: usize,
449 tx_vsizes: HashMap<Hash, usize>,
451}
452
453impl Mempool {
454 pub fn new() -> Self {
455 Self::default()
456 }
457
458 pub fn insert(&mut self, txid: Hash) -> bool {
463 self.txids.insert(txid)
464 }
465
466 pub fn insert_transaction(&mut self, tx: &Transaction) -> bool {
468 self.insert_transaction_with_witness(tx, None)
469 }
470
471 pub fn insert_transaction_with_witness(
475 &mut self,
476 tx: &Transaction,
477 witnesses: Option<&[Witness]>,
478 ) -> bool {
479 let txid = crate::block::calculate_tx_id(tx);
480 if !self.txids.insert(txid) {
481 return false;
482 }
483 let vsize = calculate_transaction_size_vbytes(tx, witnesses);
484 self.total_vbytes = self.total_vbytes.saturating_add(vsize);
485 self.tx_vsizes.insert(txid, vsize);
486 let mut outpoints = Vec::with_capacity(tx.inputs.len());
487 if !is_coinbase(tx) {
488 for input in &tx.inputs {
489 self.spent_outpoints.insert(input.prevout);
490 outpoints.push(input.prevout);
491 }
492 }
493 self.tx_spent_outpoints.insert(txid, outpoints);
494 true
495 }
496
497 pub fn contains(&self, txid: &Hash) -> bool {
498 self.txids.contains(txid)
499 }
500
501 pub fn spends_outpoint(&self, outpoint: &OutPoint) -> bool {
502 self.spent_outpoints.contains(outpoint)
503 }
504
505 pub fn remove(&mut self, txid: &Hash) -> bool {
506 if !self.txids.remove(txid) {
507 return false;
508 }
509 if let Some(vsize) = self.tx_vsizes.remove(txid) {
510 self.total_vbytes = self.total_vbytes.saturating_sub(vsize);
511 }
512 if let Some(outpoints) = self.tx_spent_outpoints.remove(txid) {
513 for op in outpoints {
514 self.spent_outpoints.remove(&op);
515 }
516 }
517 true
518 }
519
520 pub fn remove_spending_any(&mut self, outpoints: &HashSet<OutPoint>) -> Vec<Hash> {
522 if outpoints.is_empty() {
523 return Vec::new();
524 }
525 let to_remove: Vec<Hash> = self
526 .tx_spent_outpoints
527 .iter()
528 .filter(|(_, ops)| ops.iter().any(|op| outpoints.contains(op)))
529 .map(|(id, _)| *id)
530 .collect();
531 let mut removed = Vec::new();
532 for id in to_remove {
533 if self.remove(&id) {
534 removed.push(id);
535 }
536 }
537 removed
538 }
539
540 pub fn len(&self) -> usize {
541 self.txids.len()
542 }
543
544 pub fn is_empty(&self) -> bool {
545 self.txids.is_empty()
546 }
547
548 pub fn iter(&self) -> impl Iterator<Item = &Hash> {
549 self.txids.iter()
550 }
551
552 pub fn clear(&mut self) {
553 self.txids.clear();
554 self.spent_outpoints.clear();
555 self.tx_spent_outpoints.clear();
556 self.total_vbytes = 0;
557 self.tx_vsizes.clear();
558 }
559
560 pub fn total_vbytes(&self) -> usize {
562 self.total_vbytes
563 }
564}
565
566pub(crate) fn mempool_size_limits_exceeded(
568 mempool: &Mempool,
569 additional_vsize: usize,
570 max_txs: usize,
571 max_bytes: usize,
572) -> bool {
573 if mempool.len() >= max_txs {
574 return true;
575 }
576 max_bytes > 0 && mempool.total_vbytes().saturating_add(additional_vsize) > max_bytes
577}
578
579#[derive(Debug, Clone, PartialEq, Eq)]
581pub enum MempoolResult {
582 Accepted,
583 Rejected(String),
584}
585
586pub fn update_mempool_after_block(
647 mempool: &mut Mempool,
648 block: &crate::types::Block,
649 _utxo_set: &crate::types::UtxoSet,
650) -> Result<Vec<Hash>> {
651 let mut removed = Vec::new();
652
653 for tx in &block.transactions {
655 let tx_id = crate::block::calculate_tx_id(tx);
656 if mempool.remove(&tx_id) {
657 removed.push(tx_id);
658 }
659 }
660
661 let mut spent_by_block = HashSet::new();
663 for tx in &block.transactions {
664 if !is_coinbase(tx) {
665 for input in &tx.inputs {
666 spent_by_block.insert(input.prevout);
667 }
668 }
669 }
670 removed.extend(mempool.remove_spending_any(&spent_by_block));
671
672 Ok(removed)
673}
674
675pub fn update_mempool_after_block_with_lookup<F>(
690 mempool: &mut Mempool,
691 block: &crate::types::Block,
692 get_tx_by_id: F,
693) -> Result<Vec<Hash>>
694where
695 F: Fn(&Hash) -> Option<crate::types::Transaction>,
696{
697 let mut removed = Vec::new();
698
699 for tx in &block.transactions {
701 let tx_id = crate::block::calculate_tx_id(tx);
702 if mempool.remove(&tx_id) {
703 removed.push(tx_id);
704 }
705 }
706
707 let mut spent_outpoints = std::collections::HashSet::new();
710 for tx in &block.transactions {
711 if !crate::transaction::is_coinbase(tx) {
712 for input in &tx.inputs {
713 spent_outpoints.insert(input.prevout);
714 }
715 }
716 }
717
718 let mut invalid_tx_ids = Vec::new();
720 for &tx_id in mempool.iter() {
721 if let Some(tx) = get_tx_by_id(&tx_id) {
722 for input in &tx.inputs {
724 if spent_outpoints.contains(&input.prevout) {
725 invalid_tx_ids.push(tx_id);
726 break;
727 }
728 }
729 }
730 }
731
732 for tx_id in invalid_tx_ids {
734 if mempool.remove(&tx_id) {
735 removed.push(tx_id);
736 }
737 }
738
739 Ok(removed)
740}
741
742pub(crate) fn check_mempool_rules(
744 tx: &Transaction,
745 witnesses: Option<&[Witness]>,
746 fee: Integer,
747 mempool: &Mempool,
748) -> Result<bool> {
749 let vsize = calculate_transaction_size_vbytes(tx, witnesses);
751 if vsize == 0 {
752 return Ok(false);
753 }
754
755 let config = crate::config::get_consensus_config_ref();
756 let min_fee_rate = config.mempool.min_relay_fee_rate;
757 let min_tx_fee = config.mempool.min_tx_fee;
758
759 if fee < min_tx_fee {
760 return Ok(false);
761 }
762
763 let required_fee = (min_fee_rate as u128).saturating_mul(vsize as u128);
764 if (fee as u128) < required_fee {
765 return Ok(false);
766 }
767
768 let max_bytes = (config.mempool.max_mempool_mb as usize).saturating_mul(1_000_000);
769 if mempool_size_limits_exceeded(mempool, vsize, config.mempool.max_mempool_txs, max_bytes) {
770 return Ok(false);
771 }
772
773 Ok(true)
774}
775
776fn has_conflicts(tx: &Transaction, mempool: &Mempool) -> Result<bool> {
778 for input in &tx.inputs {
779 if mempool.spends_outpoint(&input.prevout) {
780 return Ok(true);
781 }
782 }
783
784 Ok(false)
785}
786
787fn verify_mempool_scripts(
789 tx: &Transaction,
790 witnesses: Option<&[Witness]>,
791 utxo_set: &UtxoSet,
792 height: Natural,
793 _time_context: Option<TimeContext>,
794 flags: u32,
795 network: Network,
796) -> Result<Option<String>> {
797 let mut prevout_values = Vec::with_capacity(tx.inputs.len());
798 let mut prevout_scripts: Vec<&[u8]> = Vec::with_capacity(tx.inputs.len());
799 for input in &tx.inputs {
800 if let Some(utxo) = utxo_set.get(&input.prevout) {
801 prevout_values.push(utxo.value);
802 prevout_scripts.push(utxo.script_pubkey.as_ref());
803 } else {
804 prevout_values.push(0);
805 prevout_scripts.push(&[]);
806 }
807 }
808
809 for (i, input) in tx.inputs.iter().enumerate() {
810 let Some(utxo) = utxo_set.get(&input.prevout) else {
811 continue;
812 };
813 let witness = witnesses.and_then(|wits| wits.get(i));
814 let is_valid = match verify_script_with_context_full(
815 &input.script_sig,
816 utxo.script_pubkey.as_ref(),
817 witness,
818 flags,
819 tx,
820 i,
821 &prevout_values,
822 &prevout_scripts,
823 Some(height),
824 None,
825 network,
826 SigVersion::Base,
827 #[cfg(feature = "production")]
828 None,
829 None,
830 #[cfg(feature = "production")]
831 None,
832 #[cfg(feature = "production")]
833 None,
834 #[cfg(feature = "production")]
835 None,
836 #[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
837 None,
838 ) {
839 Ok(valid) => valid,
840 Err(error) => crate::script_err_as_false(error)?,
841 };
842 if !is_valid {
843 return Ok(Some(format!("Invalid script at input {i}")));
844 }
845 }
846
847 Ok(None)
848}
849
850fn input_allowed_for_replacement(
852 input: &TransactionInput,
853 existing_tx: &Transaction,
854 utxo_set: &UtxoSet,
855) -> bool {
856 if utxo_set.contains_key(&input.prevout) {
857 return true;
858 }
859 if existing_tx
860 .inputs
861 .iter()
862 .any(|existing_input| existing_input.prevout == input.prevout)
863 {
864 return true;
865 }
866 let existing_id = crate::block::calculate_tx_id(existing_tx);
867 (input.prevout.hash == existing_id)
868 && ((input.prevout.index as usize) < existing_tx.outputs.len())
869}
870
871#[cfg_attr(feature = "spec-lock", spec_locked("9.1.1", "CheckFinalTxAtTip"))]
912pub fn is_final_tx(tx: &Transaction, height: Natural, block_time: Natural) -> bool {
913 use crate::constants::SEQUENCE_FINAL;
914
915 if tx.lock_time == 0 {
917 return true;
918 }
919
920 let locktime_satisfied = if (tx.lock_time as u32) < LOCKTIME_THRESHOLD {
926 (tx.lock_time as Natural) < height
928 } else {
929 (tx.lock_time as Natural) < block_time
931 };
932
933 if locktime_satisfied {
934 return true;
935 }
936
937 for input in &tx.inputs {
941 if (input.sequence as u32) != SEQUENCE_FINAL {
942 return false;
943 }
944 }
945
946 true
948}
949
950pub fn relative_lock_unsatisfied(
955 tx: &Transaction,
956 utxo_set: &UtxoSet,
957 height: Natural,
958 block_time: Natural,
959 prev_mtps: Option<&[i64]>,
960 network: Network,
961) -> Result<bool> {
962 use crate::activation::{ForkActivationTable, IsForkActive};
963 use crate::locktime::{extract_sequence_type_flag, is_sequence_disabled};
964 use crate::sequence_locks::{LOCKTIME_VERIFY_SEQUENCE, sequence_locks};
965 use crate::types::ForkId;
966
967 if !crate::sequence_locks::version_enforces_relative_lock(tx.version)
968 || !ForkActivationTable::from_network(network).is_fork_active(ForkId::Bip112, height)
969 {
970 return Ok(false);
971 }
972
973 let mut prev_heights = Vec::with_capacity(tx.inputs.len());
974 let mut mtps = Vec::with_capacity(tx.inputs.len());
975 let mut unknown_time = false;
976 for (i, input) in tx.inputs.iter().enumerate() {
977 let seq = input.sequence as u32;
978 let coin_height = utxo_set
979 .get(&input.prevout)
980 .map(|utxo| utxo.height)
981 .unwrap_or(height);
982 prev_heights.push(coin_height);
983 let supplied = prev_mtps.and_then(|all| all.get(i).copied()).unwrap_or(-1);
984 if is_sequence_disabled(seq) || !extract_sequence_type_flag(seq) {
985 mtps.push(-1);
986 } else if supplied < 0 {
987 unknown_time = true;
988 mtps.push(-1);
989 } else {
990 mtps.push(supplied);
991 }
992 }
993
994 if unknown_time {
995 return Ok(true);
996 }
997 let satisfied = sequence_locks(
998 tx,
999 LOCKTIME_VERIFY_SEQUENCE,
1000 &prev_heights,
1001 height,
1002 block_time,
1003 Some(&mtps),
1004 )?;
1005 Ok(!satisfied)
1006}
1007
1008pub fn signals_rbf(tx: &Transaction) -> bool {
1013 for input in &tx.inputs {
1014 if (input.sequence as u32) < SEQUENCE_FINAL {
1015 return true;
1016 }
1017 }
1018 false
1019}
1020
1021fn calculate_transaction_size_vbytes(tx: &Transaction, stacks: Option<&[Witness]>) -> usize {
1025 use crate::segwit::transaction_weight_from_stacks;
1026 use crate::witness::weight_to_vsize;
1027 match transaction_weight_from_stacks(tx, stacks) {
1028 Ok(weight) => weight_to_vsize(weight) as usize,
1029 Err(_) => calculate_transaction_size(tx),
1030 }
1031}
1032
1033fn witnessed_vsize(tx: &Transaction, stacks: Option<&[Witness]>, mempool: &Mempool) -> usize {
1035 if stacks.is_none() {
1036 let txid = crate::block::calculate_tx_id(tx);
1037 if let Some(&stored) = mempool.tx_vsizes.get(&txid) {
1038 return stored;
1039 }
1040 }
1041 calculate_transaction_size_vbytes(tx, stacks)
1042}
1043
1044pub fn incremental_relay_bump(vsize: u64) -> i64 {
1048 i64::try_from((100u128).saturating_mul(u128::from(vsize)).div_ceil(1000)).unwrap_or(i64::MAX)
1049}
1050
1051pub fn incremental_relay_bump_for(tx: &Transaction) -> i64 {
1053 incremental_relay_bump(calculate_transaction_size_vbytes(tx, None) as u64)
1054}
1055
1056pub fn has_conflict_with_tx(new_tx: &Transaction, existing_tx: &Transaction) -> bool {
1061 for new_input in &new_tx.inputs {
1062 for existing_input in &existing_tx.inputs {
1063 if new_input.prevout == existing_input.prevout {
1064 return true;
1065 }
1066 }
1067 }
1068 false
1069}
1070
1071pub(crate) fn creates_new_dependencies(
1077 new_tx: &Transaction,
1078 existing_tx: &Transaction,
1079 utxo_set: &UtxoSet,
1080 _mempool: &Mempool,
1081) -> Result<bool> {
1082 for input in &new_tx.inputs {
1083 if utxo_set.contains_key(&input.prevout) {
1085 continue;
1086 }
1087
1088 let mut found_in_existing = false;
1090 for existing_input in &existing_tx.inputs {
1091 if existing_input.prevout == input.prevout {
1092 found_in_existing = true;
1093 break;
1094 }
1095 }
1096
1097 if found_in_existing {
1098 continue;
1099 }
1100
1101 if input_allowed_for_replacement(input, existing_tx, utxo_set) {
1102 continue;
1103 }
1104
1105 return Ok(true);
1106 }
1107
1108 Ok(false)
1109}
1110
1111fn script_opcode_advance(script: &[u8], pc: usize) -> usize {
1113 let opcode = script[pc];
1114 match opcode {
1115 0x01..=0x4b => 1 + opcode as usize,
1116 0x4c => {
1117 if pc + 1 < script.len() {
1118 2 + script[pc + 1] as usize
1119 } else {
1120 1
1121 }
1122 }
1123 0x4d => {
1124 if pc + 2 < script.len() {
1125 3 + u16::from_le_bytes([script[pc + 1], script[pc + 2]]) as usize
1126 } else {
1127 1
1128 }
1129 }
1130 0x4e => {
1131 if pc + 4 < script.len() {
1132 5 + u32::from_le_bytes([
1133 script[pc + 1],
1134 script[pc + 2],
1135 script[pc + 3],
1136 script[pc + 4],
1137 ]) as usize
1138 } else {
1139 1
1140 }
1141 }
1142 _ => 1,
1143 }
1144}
1145
1146#[inline]
1148fn is_disabled_policy_opcode(opcode: u8) -> bool {
1149 use crate::opcodes::{
1150 OP_2DIV, OP_2MUL, OP_AND, OP_CAT, OP_DIV, OP_INVERT, OP_LEFT, OP_LSHIFT, OP_MOD, OP_MUL,
1151 OP_OR, OP_RIGHT, OP_RSHIFT, OP_SUBSTR, OP_VER, OP_VERIF, OP_VERNOTIF, OP_XOR,
1152 };
1153 matches!(
1154 opcode,
1155 OP_VER
1156 | OP_VERIF
1157 | OP_VERNOTIF
1158 | OP_CAT
1159 | OP_SUBSTR
1160 | OP_LEFT
1161 | OP_RIGHT
1162 | OP_INVERT
1163 | OP_AND
1164 | OP_OR
1165 | OP_XOR
1166 | OP_2MUL
1167 | OP_2DIV
1168 | OP_MUL
1169 | OP_DIV
1170 | OP_MOD
1171 | OP_LSHIFT
1172 | OP_RSHIFT
1173 )
1174}
1175
1176pub(crate) fn is_standard_script(script: &ByteString) -> Result<bool> {
1178 if script.is_empty() {
1179 return Ok(false);
1180 }
1181
1182 if script.len() > MAX_SCRIPT_SIZE {
1183 return Ok(false);
1184 }
1185
1186 if script[0] == 0x6a {
1189 return Ok(script.len() <= 83);
1191 }
1192
1193 let mut pc = 0;
1195 while pc < script.len() {
1196 let opcode = script[pc];
1197 if is_disabled_policy_opcode(opcode) {
1198 return Ok(false);
1199 }
1200 let advance = script_opcode_advance(script, pc);
1201 if advance == 0 {
1202 break;
1203 }
1204 pc += advance;
1205 }
1206
1207 Ok(true)
1208}
1209
1210#[deprecated(note = "Use crate::block::calculate_tx_id instead")]
1215#[cfg_attr(feature = "spec-lock", spec_locked("5.1", "CalculateTxId"))]
1216pub fn calculate_tx_id(tx: &Transaction) -> Hash {
1217 crate::block::calculate_tx_id(tx)
1218}
1219
1220fn calculate_transaction_size(tx: &Transaction) -> usize {
1224 use crate::transaction::calculate_transaction_size as tx_size;
1225 tx_size(tx)
1226}
1227
1228fn is_coinbase(tx: &Transaction) -> bool {
1230 #[cfg(feature = "production")]
1232 {
1233 use crate::optimizations::constant_folding::is_zero_hash;
1234 tx.inputs.len() == 1
1235 && is_zero_hash(&tx.inputs[0].prevout.hash)
1236 && tx.inputs[0].prevout.index == 0xffffffff
1237 }
1238
1239 #[cfg(not(feature = "production"))]
1240 {
1241 tx.inputs.len() == 1
1242 && tx.inputs[0].prevout.hash == [0u8; 32]
1243 && tx.inputs[0].prevout.index == 0xffffffff
1244 }
1245}
1246
1247pub const DEFAULT_BYTES_PER_SIGOP: u64 = 20;
1259
1260pub const MAX_STANDARD_TX_SIGOPS_COST: u64 = crate::constants::MAX_BLOCK_SIGOPS_COST / 5;
1263
1264pub fn sigop_adjusted_vsize(weight: u64, sigop_cost: u64, bytes_per_sigop: u64) -> u64 {
1271 let sigop_weight = sigop_cost.saturating_mul(bytes_per_sigop);
1272 weight.max(sigop_weight).div_ceil(4)
1273}
1274
1275#[cfg(test)]
1276mod tests {
1277 use super::*;
1278 use crate::opcodes::*;
1279
1280 #[test]
1281 fn sigop_adjusted_vsize_matches_core_rule() {
1282 assert_eq!(sigop_adjusted_vsize(400, 1, DEFAULT_BYTES_PER_SIGOP), 100);
1284 assert_eq!(sigop_adjusted_vsize(100, 10, DEFAULT_BYTES_PER_SIGOP), 50);
1286 assert_eq!(sigop_adjusted_vsize(201, 0, DEFAULT_BYTES_PER_SIGOP), 51);
1288 assert_eq!(DEFAULT_BYTES_PER_SIGOP, 20);
1290 assert_eq!(MAX_STANDARD_TX_SIGOPS_COST, 16_000);
1291 }
1292
1293 #[test]
1294 fn test_disabled_opcode_spend_is_rejected() {
1295 let prevout = OutPoint {
1296 hash: [1; 32],
1297 index: 0,
1298 };
1299 let mut utxo_set = UtxoSet::default();
1300 utxo_set.insert(
1301 prevout,
1302 std::sync::Arc::new(UTXO {
1303 value: 10_000,
1304 script_pubkey: vec![OP_MUL].into(),
1305 height: 0,
1306 is_coinbase: false,
1307 }),
1308 );
1309 let tx = Transaction {
1310 version: 1,
1311 inputs: vec![TransactionInput {
1312 prevout,
1313 script_sig: vec![],
1314 sequence: 0xffffffff,
1315 }]
1316 .into(),
1317 outputs: vec![TransactionOutput {
1318 value: 0,
1319 script_pubkey: vec![OP_1],
1320 }]
1321 .into(),
1322 lock_time: 0,
1323 };
1324 let result = accept_to_memory_pool(
1325 &tx,
1326 None,
1327 &utxo_set,
1328 &Mempool::new(),
1329 200,
1330 Some(TimeContext {
1331 network_time: 1_700_000_000,
1332 median_time_past: 0,
1333 }),
1334 Network::Mainnet,
1335 None,
1336 )
1337 .expect("a disabled opcode is a rejection");
1338 assert!(matches!(result, MempoolResult::Rejected(_)));
1339 }
1340
1341 #[test]
1342 fn test_accept_to_memory_pool_valid() {
1343 let tx = create_valid_transaction();
1345 let utxo_set = create_test_utxo_set();
1346 let mempool = Mempool::new();
1347
1348 let time_context = Some(TimeContext {
1350 network_time: 1234567890,
1351 median_time_past: 1234567890,
1352 });
1353 let result = accept_to_memory_pool(
1354 &tx,
1355 None,
1356 &utxo_set,
1357 &mempool,
1358 100,
1359 time_context,
1360 Network::Mainnet,
1361 None,
1362 )
1363 .unwrap();
1364 assert!(matches!(result, MempoolResult::Accepted));
1365 }
1366
1367 #[test]
1368 fn relative_height_lock_is_not_accepted_until_it_is_old_enough() {
1369 let mut tx = create_valid_transaction();
1370 tx.version = 2;
1371 tx.inputs[0].sequence = 10;
1372 let prevout = tx.inputs[0].prevout;
1373 let time_context = Some(TimeContext {
1374 network_time: 1_700_000_000,
1375 median_time_past: 1_700_000_000,
1376 });
1377 let mempool = Mempool::new();
1378 let young = utxo_confirmed_at(prevout, 499_995);
1379 let result = accept_to_memory_pool(
1380 &tx,
1381 None,
1382 &young,
1383 &mempool,
1384 500_000,
1385 time_context,
1386 Network::Mainnet,
1387 None,
1388 )
1389 .unwrap();
1390 assert!(matches!(result, MempoolResult::Rejected(_)));
1391
1392 let old = utxo_confirmed_at(prevout, 499_000);
1393 let result = accept_to_memory_pool(
1394 &tx,
1395 None,
1396 &old,
1397 &mempool,
1398 500_000,
1399 time_context,
1400 Network::Mainnet,
1401 None,
1402 )
1403 .unwrap();
1404 assert!(matches!(result, MempoolResult::Accepted));
1405
1406 tx.version = 1;
1407 let result = accept_to_memory_pool(
1408 &tx,
1409 None,
1410 &young,
1411 &mempool,
1412 500_000,
1413 time_context,
1414 Network::Mainnet,
1415 None,
1416 )
1417 .unwrap();
1418 assert!(matches!(result, MempoolResult::Accepted));
1419 }
1420
1421 #[test]
1422 fn time_relative_lock_uses_prior_median() {
1423 const SEQUENCE_LOCKTIME_TYPE_FLAG: u64 = 0x0040_0000;
1424 let mut tx = create_valid_transaction();
1425 tx.version = 2;
1426 tx.inputs[0].sequence = SEQUENCE_LOCKTIME_TYPE_FLAG | 2;
1427 let prevout = tx.inputs[0].prevout;
1428 let utxo = utxo_confirmed_at(prevout, 499_000);
1429 let mempool = Mempool::new();
1430 let prior = [1_000_000i64];
1431 let inside = Some(TimeContext {
1432 network_time: 1_001_023,
1433 median_time_past: 1_001_023,
1434 });
1435 let rejected = accept_to_memory_pool(
1436 &tx,
1437 None,
1438 &utxo,
1439 &mempool,
1440 500_000,
1441 inside,
1442 Network::Mainnet,
1443 Some(&prior),
1444 )
1445 .unwrap();
1446 assert!(matches!(rejected, MempoolResult::Rejected(_)));
1447
1448 let old_enough = Some(TimeContext {
1449 network_time: 1_001_024,
1450 median_time_past: 1_001_024,
1451 });
1452 let accepted = accept_to_memory_pool(
1453 &tx,
1454 None,
1455 &utxo,
1456 &mempool,
1457 500_000,
1458 old_enough,
1459 Network::Mainnet,
1460 Some(&prior),
1461 )
1462 .unwrap();
1463 assert!(matches!(accepted, MempoolResult::Accepted));
1464 }
1465
1466 #[test]
1467 fn test_accept_to_memory_pool_duplicate() {
1468 let tx = create_valid_transaction();
1469 let utxo_set = create_test_utxo_set();
1470 let mut mempool = Mempool::new();
1471 mempool.insert(crate::block::calculate_tx_id(&tx));
1472
1473 let time_context = Some(TimeContext {
1474 network_time: 1234567890,
1475 median_time_past: 1234567890,
1476 });
1477 let result = accept_to_memory_pool(
1478 &tx,
1479 None,
1480 &utxo_set,
1481 &mempool,
1482 100,
1483 time_context,
1484 Network::Mainnet,
1485 None,
1486 )
1487 .unwrap();
1488 assert!(matches!(result, MempoolResult::Rejected(_)));
1489 }
1490
1491 #[test]
1492 fn test_is_standard_tx_valid() {
1493 let tx = create_valid_transaction();
1494 assert!(is_standard_tx(&tx).unwrap());
1495 }
1496
1497 #[test]
1498 fn test_is_standard_tx_too_large() {
1499 let mut tx = create_valid_transaction();
1500 for _ in 0..MAX_INPUTS {
1503 tx.inputs.push(create_dummy_input());
1504 }
1505 assert!(!is_standard_tx(&tx).unwrap());
1507 }
1508
1509 #[test]
1510 fn test_replacement_checks_all_requirements() {
1511 let utxo_set = create_test_utxo_set();
1512 let mempool = Mempool::new();
1513
1514 let mut existing_tx = create_valid_transaction();
1516 existing_tx.inputs[0].sequence = (SEQUENCE_RBF as u64) - 1;
1518 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1525 new_tx.outputs[0].value = 8000; new_tx.outputs[0].value = 7999; let result = replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1531 assert!(result, "Valid RBF replacement should be accepted");
1532 }
1533
1534 #[test]
1535 fn test_replacement_checks_no_rbf_signal() {
1536 let utxo_set = create_test_utxo_set();
1537 let mempool = Mempool::new();
1538
1539 let existing_tx = create_valid_transaction();
1540 let mut new_tx = existing_tx.clone();
1541 new_tx.outputs[0].value = 500;
1542
1543 assert!(!signals_rbf(&existing_tx));
1544 assert!(replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1545 }
1546
1547 #[test]
1548 fn test_replacement_checks_no_conflict() {
1549 let mut utxo_set = create_test_utxo_set();
1550 let new_outpoint = OutPoint {
1552 hash: [2; 32],
1553 index: 0,
1554 };
1555 let new_utxo = UTXO {
1556 value: 10000,
1557 script_pubkey: vec![OP_1].into(),
1558 height: 0,
1559 is_coinbase: false,
1560 };
1561 utxo_set.insert(new_outpoint, std::sync::Arc::new(new_utxo));
1562
1563 let mempool = Mempool::new();
1564
1565 let mut existing_tx = create_valid_transaction();
1566 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1567
1568 let mut new_tx = create_valid_transaction();
1570 new_tx.inputs[0].prevout.hash = [2; 32]; new_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1572 new_tx.outputs[0].value = 5000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1577 }
1578
1579 #[test]
1580 fn test_replacement_checks_fee_rate_too_low() {
1581 let utxo_set = create_test_utxo_set();
1582 let mempool = Mempool::new();
1583
1584 let mut existing_tx = create_valid_transaction();
1586 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1587 existing_tx.outputs[0].value = 5000; let mut new_tx = existing_tx.clone();
1591 new_tx.outputs[0].value = 4999; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1595 }
1596
1597 #[test]
1598 fn test_replacement_checks_absolute_fee_insufficient() {
1599 let utxo_set = create_test_utxo_set();
1600 let mempool = Mempool::new();
1601
1602 let mut existing_tx = create_valid_transaction();
1604 existing_tx.inputs[0].sequence = (SEQUENCE_RBF as u64) - 1;
1605 existing_tx.outputs[0].value = 9000; let bump =
1609 incremental_relay_bump(calculate_transaction_size_vbytes(&existing_tx, None) as u64);
1610 let mut new_tx = existing_tx.clone();
1611 new_tx.outputs[0].value = 10_000 - (1_000 + bump - 1);
1612
1613 assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1614 }
1615
1616 #[test]
1621 fn test_accept_to_memory_pool_coinbase() {
1622 let coinbase_tx = create_coinbase_transaction();
1623 let utxo_set = UtxoSet::default();
1624 let mempool = Mempool::new();
1625 let time_context = Some(TimeContext {
1627 network_time: 0,
1628 median_time_past: 0,
1629 });
1630 let result = accept_to_memory_pool(
1631 &coinbase_tx,
1632 None,
1633 &utxo_set,
1634 &mempool,
1635 100,
1636 time_context,
1637 Network::Mainnet,
1638 None,
1639 )
1640 .unwrap();
1641 assert!(matches!(result, MempoolResult::Rejected(_)));
1642 }
1643
1644 #[test]
1645 fn test_is_standard_tx_large_script() {
1646 let mut tx = create_valid_transaction();
1647 tx.inputs[0].script_sig = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1649
1650 let result = is_standard_tx(&tx).unwrap();
1651 assert!(!result);
1652 }
1653
1654 #[test]
1655 fn test_is_standard_tx_large_output_script() {
1656 let mut tx = create_valid_transaction();
1657 tx.outputs[0].script_pubkey = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1659
1660 let result = is_standard_tx(&tx).unwrap();
1661 assert!(!result);
1662 }
1663
1664 #[test]
1665 fn test_replacement_checks_new_unconfirmed_dependency() {
1666 let utxo_set = create_test_utxo_set();
1667 let mempool = Mempool::new();
1668
1669 let mut existing_tx = create_valid_transaction();
1671 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1672
1673 let mut new_tx = existing_tx.clone();
1675 new_tx.inputs.push(TransactionInput {
1676 prevout: OutPoint {
1677 hash: [99; 32],
1678 index: 0,
1679 }, script_sig: vec![],
1681 sequence: SEQUENCE_RBF as u64,
1682 });
1683 new_tx.outputs[0].value = 7000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1687 }
1688
1689 #[test]
1690 fn test_has_conflict_with_tx_true() {
1691 let tx1 = create_valid_transaction();
1692 let mut tx2 = create_valid_transaction();
1693 tx2.inputs[0].prevout = tx1.inputs[0].prevout; assert!(has_conflict_with_tx(&tx2, &tx1));
1696 }
1697
1698 #[test]
1699 fn test_has_conflict_with_tx_false() {
1700 let tx1 = create_valid_transaction();
1701 let mut tx2 = create_valid_transaction();
1702 tx2.inputs[0].prevout.hash = [2; 32]; assert!(!has_conflict_with_tx(&tx2, &tx1));
1705 }
1706
1707 #[test]
1708 fn test_replacement_checks_minimum_relay_fee() {
1709 let utxo_set = create_test_utxo_set();
1710 let mempool = Mempool::new();
1711
1712 let mut existing_tx = create_valid_transaction();
1714 existing_tx.inputs[0].sequence = (SEQUENCE_RBF as u64) - 1;
1715 existing_tx.outputs[0].value = 9500; let bump =
1718 incremental_relay_bump(calculate_transaction_size_vbytes(&existing_tx, None) as u64);
1719 let mut new_tx = existing_tx.clone();
1720 new_tx.outputs[0].value = 10_000 - (500 + bump - 1);
1721 assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1722 new_tx.outputs[0].value = 10_000 - (500 + bump);
1723 assert!(replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1724 }
1725
1726 #[test]
1727 fn test_check_mempool_rules_low_fee() {
1728 let tx = create_valid_transaction();
1729 let fee = 1; let mempool = Mempool::new();
1731
1732 let result = check_mempool_rules(&tx, None, fee, &mempool).unwrap();
1733 assert!(!result);
1734 }
1735
1736 #[test]
1737 fn test_check_mempool_rules_high_fee() {
1738 let tx = create_valid_transaction();
1739 let fee = 10000; let mempool = Mempool::new();
1741
1742 let result = check_mempool_rules(&tx, None, fee, &mempool).unwrap();
1743 assert!(result);
1744 }
1745
1746 #[test]
1747 fn test_mempool_total_vbytes_tracking() {
1748 let mut mempool = Mempool::new();
1749 let tx = create_valid_transaction();
1750 let vsize = calculate_transaction_size_vbytes(&tx, None);
1751 let txid = crate::block::calculate_tx_id(&tx);
1752
1753 mempool.insert_transaction(&tx);
1754 assert_eq!(mempool.total_vbytes(), vsize);
1755
1756 mempool.remove(&txid);
1757 assert_eq!(mempool.total_vbytes(), 0);
1758 }
1759
1760 #[test]
1761 fn mempool_vsize_includes_witness_stacks() {
1762 let tx = create_valid_transaction();
1763 let bare = calculate_transaction_size_vbytes(&tx, None);
1764 let empty_stacks: Vec<Witness> = vec![vec![]];
1765 assert_eq!(
1766 calculate_transaction_size_vbytes(&tx, Some(&empty_stacks)),
1767 bare
1768 );
1769
1770 let heavy_stacks: Vec<Witness> = vec![vec![vec![0u8; 4_000]]];
1771 let heavy = calculate_transaction_size_vbytes(&tx, Some(&heavy_stacks));
1772 assert!(heavy > bare);
1773
1774 let fee = (heavy as i64) - 1;
1775 assert!(fee >= 1_000);
1776 let mempool = Mempool::new();
1777 assert!(check_mempool_rules(&tx, None, fee, &mempool).unwrap());
1778 assert!(!check_mempool_rules(&tx, Some(&heavy_stacks), fee, &mempool).unwrap());
1779
1780 let mut pool = Mempool::new();
1781 assert!(pool.insert_transaction_with_witness(&tx, Some(&heavy_stacks)));
1782 assert_eq!(pool.total_vbytes(), heavy);
1783 }
1784
1785 #[test]
1786 fn test_mempool_byte_limit_exceeded() {
1787 let mut mempool = Mempool::new();
1788 let tx = create_valid_transaction();
1789 let vsize = calculate_transaction_size_vbytes(&tx, None);
1790 mempool.insert_transaction(&tx);
1791
1792 let at_limit = mempool.total_vbytes();
1793 assert!(!mempool_size_limits_exceeded(
1794 &mempool, 0, 1_000_000, at_limit
1795 ));
1796 assert!(mempool_size_limits_exceeded(
1797 &mempool, 1, 1_000_000, at_limit
1798 ));
1799 assert!(!mempool_size_limits_exceeded(
1800 &mempool,
1801 vsize,
1802 1_000_000,
1803 at_limit.saturating_add(vsize)
1804 ));
1805 }
1806
1807 #[test]
1808 fn test_check_mempool_rules_full_mempool() {
1809 let tx = create_valid_transaction();
1810 let fee = 10000;
1811 let mut mempool = Mempool::new();
1812
1813 for i in 0..100_000 {
1815 let mut hash = [0u8; 32];
1816 hash[0] = (i & 0xff) as u8;
1817 hash[1] = ((i >> 8) & 0xff) as u8;
1818 hash[2] = ((i >> 16) & 0xff) as u8;
1819 hash[3] = ((i >> 24) & 0xff) as u8;
1820 mempool.insert(hash);
1821 }
1822
1823 assert_eq!(mempool.len(), 100_000);
1824
1825 let result = check_mempool_rules(&tx, None, fee, &mempool).unwrap();
1826 assert!(!result);
1827 }
1828
1829 #[test]
1830 fn test_has_conflicts_no_conflicts() {
1831 let tx = create_valid_transaction();
1832 let mempool = Mempool::new();
1833
1834 let result = has_conflicts(&tx, &mempool).unwrap();
1835 assert!(!result);
1836 }
1837
1838 #[test]
1839 fn test_has_conflicts_with_conflicts() {
1840 let tx = create_valid_transaction();
1841 let mut mempool = Mempool::new();
1842
1843 let mut pool_tx = tx.clone();
1845 pool_tx.version = 2;
1846 mempool.insert_transaction(&pool_tx);
1847
1848 let result = has_conflicts(&tx, &mempool).unwrap();
1849 assert!(result);
1850 }
1851
1852 #[test]
1853 fn test_signals_rbf_true() {
1854 let mut tx = create_valid_transaction();
1855 tx.inputs[0].sequence = 0xfffffffd; assert!(signals_rbf(&tx));
1858 }
1859
1860 #[test]
1861 fn test_signals_rbf_locktime_sequence_opts_in() {
1862 let mut tx = create_valid_transaction();
1863 tx.inputs[0].sequence = 0xfffffffe;
1864
1865 assert!(signals_rbf(&tx));
1866 }
1867
1868 #[test]
1869 fn test_signals_rbf_false() {
1870 let tx = create_valid_transaction(); assert!(!signals_rbf(&tx));
1873 }
1874
1875 #[test]
1876 fn test_calculate_fee_rate() {
1877 let tx = create_valid_transaction();
1878 let utxo_set = create_test_utxo_set();
1879 let fee = calculate_fee(&tx, &utxo_set);
1880
1881 assert!(fee.is_ok());
1883 }
1884
1885 #[test]
1886 fn test_creates_new_dependencies_no_new() {
1887 let new_tx = create_valid_transaction();
1888 let existing_tx = create_valid_transaction();
1889 let mempool = Mempool::new();
1890
1891 let utxo_set = create_test_utxo_set();
1892 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1893 assert!(!result);
1894 }
1895
1896 #[test]
1897 fn test_creates_new_dependencies_with_new() {
1898 let mut new_tx = create_valid_transaction();
1899 let existing_tx = create_valid_transaction();
1900 let mempool = Mempool::new();
1901
1902 new_tx.inputs[0].prevout.hash = [2; 32];
1904
1905 let utxo_set = create_test_utxo_set();
1906 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1907 assert!(result);
1908 }
1909
1910 #[test]
1911 fn test_is_standard_script_empty() {
1912 let script = vec![];
1913 let result = is_standard_script(&script).unwrap();
1914 assert!(!result);
1915 }
1916
1917 #[test]
1918 fn test_is_standard_script_too_large() {
1919 let script = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1920 let result = is_standard_script(&script).unwrap();
1921 assert!(!result);
1922 }
1923
1924 #[test]
1925 fn test_is_standard_script_non_standard_opcode() {
1926 let script = vec![OP_VERIF]; let result = is_standard_script(&script).unwrap();
1928 assert!(!result);
1929 }
1930
1931 #[test]
1932 fn test_is_standard_script_valid() {
1933 let script = vec![OP_1];
1934 let result = is_standard_script(&script).unwrap();
1935 assert!(result);
1936 }
1937
1938 #[test]
1939 fn test_calculate_tx_id() {
1940 let tx = create_valid_transaction();
1941 let tx_id = crate::block::calculate_tx_id(&tx);
1942
1943 assert_eq!(tx_id.len(), 32);
1945
1946 let tx_id2 = crate::block::calculate_tx_id(&tx);
1948 assert_eq!(tx_id, tx_id2);
1949 }
1950
1951 #[test]
1952 fn test_calculate_tx_id_different_txs() {
1953 let tx1 = create_valid_transaction();
1954 let mut tx2 = tx1.clone();
1955 tx2.version = 2; let id1 = crate::block::calculate_tx_id(&tx1);
1958 let id2 = crate::block::calculate_tx_id(&tx2);
1959
1960 assert_ne!(id1, id2);
1961 }
1962
1963 #[test]
1964 fn test_calculate_transaction_size() {
1965 let tx = create_valid_transaction();
1966 let size = calculate_transaction_size(&tx);
1967
1968 assert!(size > 0);
1969
1970 let size2 = calculate_transaction_size(&tx);
1972 assert_eq!(size, size2);
1973 }
1974
1975 #[test]
1976 fn test_calculate_transaction_size_multiple_inputs_outputs() {
1977 let mut tx = create_valid_transaction();
1978 tx.inputs.push(create_dummy_input());
1979 tx.outputs.push(create_dummy_output());
1980
1981 let size = calculate_transaction_size(&tx);
1982 assert!(size > 0);
1983 }
1984
1985 #[test]
1986 fn test_is_coinbase_true() {
1987 let coinbase_tx = create_coinbase_transaction();
1988 assert!(is_coinbase(&coinbase_tx));
1989 }
1990
1991 #[test]
1992 fn test_is_coinbase_false() {
1993 let regular_tx = create_valid_transaction();
1994 assert!(!is_coinbase(®ular_tx));
1995 }
1996
1997 fn create_valid_transaction() -> Transaction {
1999 Transaction {
2000 version: 1,
2001 inputs: vec![create_dummy_input()].into(),
2002 outputs: vec![create_dummy_output()].into(),
2003 lock_time: 0,
2004 }
2005 }
2006
2007 fn create_dummy_input() -> TransactionInput {
2008 TransactionInput {
2009 prevout: OutPoint {
2010 hash: [1; 32],
2011 index: 0,
2012 },
2013 script_sig: vec![OP_1],
2014 sequence: 0xffffffff,
2015 }
2016 }
2017
2018 fn create_dummy_output() -> TransactionOutput {
2019 TransactionOutput {
2020 value: 1000,
2021 script_pubkey: vec![OP_1], }
2023 }
2024
2025 fn utxo_confirmed_at(prevout: OutPoint, height: u64) -> UtxoSet {
2026 let mut utxo_set = UtxoSet::default();
2027 utxo_set.insert(
2028 prevout,
2029 std::sync::Arc::new(UTXO {
2030 value: 10000,
2031 script_pubkey: vec![OP_1].into(),
2032 height,
2033 is_coinbase: false,
2034 }),
2035 );
2036 utxo_set
2037 }
2038
2039 fn create_test_utxo_set() -> UtxoSet {
2040 let mut utxo_set = UtxoSet::default();
2041 let outpoint = OutPoint {
2042 hash: [1; 32],
2043 index: 0,
2044 };
2045 let utxo = UTXO {
2046 value: 10000,
2047 script_pubkey: vec![OP_1].into(), height: 0,
2049 is_coinbase: false,
2050 };
2051 utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
2052 utxo_set
2053 }
2054
2055 fn create_coinbase_transaction() -> Transaction {
2056 Transaction {
2057 version: 1,
2058 inputs: vec![TransactionInput {
2059 prevout: OutPoint {
2060 hash: [0; 32],
2061 index: 0xffffffff,
2062 },
2063 script_sig: vec![],
2064 sequence: 0xffffffff,
2065 }]
2066 .into(),
2067 outputs: vec![TransactionOutput {
2068 value: 5000000000,
2069 script_pubkey: vec![],
2070 }]
2071 .into(),
2072 lock_time: 0,
2073 }
2074 }
2075}