1use crate::constants::*;
4use crate::economic::calculate_fee;
5use crate::error::{ConsensusError, Result};
6use crate::script::verify_script;
7use crate::segwit::Witness;
8use crate::transaction::{check_transaction, check_tx_inputs};
9use crate::types::*;
10use blvm_spec_lock::spec_locked;
11use std::collections::HashSet;
12
13#[spec_locked("9.1")]
33pub fn accept_to_memory_pool(
34 tx: &Transaction,
35 witnesses: Option<&[Witness]>,
36 utxo_set: &UtxoSet,
37 mempool: &Mempool,
38 height: Natural,
39 time_context: Option<TimeContext>,
40) -> Result<MempoolResult> {
41 if tx.inputs.is_empty() && tx.outputs.is_empty() {
45 return Ok(MempoolResult::Rejected(
46 "Transaction must have at least one input or output".to_string(),
47 ));
48 }
49 if is_coinbase(tx) {
50 return Ok(MempoolResult::Rejected(
51 "Coinbase transactions cannot be added to mempool".to_string(),
52 ));
53 }
54 assert!(
55 height <= i64::MAX as u64,
56 "Block height {height} must fit in i64"
57 );
58 assert!(
59 utxo_set.len() <= u32::MAX as usize,
60 "UTXO set size {} exceeds maximum",
61 utxo_set.len()
62 );
63 if let Some(wits) = witnesses {
64 assert!(
65 wits.len() == tx.inputs.len(),
66 "Witness count {} must match input count {}",
67 wits.len(),
68 tx.inputs.len()
69 );
70 }
71
72 let tx_id = crate::block::calculate_tx_id(tx);
74 assert!(tx_id != [0u8; 32], "Transaction ID must be non-zero");
76 if mempool.contains(&tx_id) {
77 return Ok(MempoolResult::Rejected(
78 "Transaction already in mempool".to_string(),
79 ));
80 }
81
82 if !matches!(check_transaction(tx)?, ValidationResult::Valid) {
84 return Ok(MempoolResult::Rejected(
85 "Invalid transaction structure".to_string(),
86 ));
87 }
88
89 let block_time = time_context.map(|ctx| ctx.median_time_past).unwrap_or(0);
92 if !is_final_tx(tx, height, block_time) {
93 return Ok(MempoolResult::Rejected(
94 "Transaction not final (locktime not satisfied)".to_string(),
95 ));
96 }
97
98 let (input_valid, fee) = check_tx_inputs(tx, utxo_set, height)?;
100 assert!(fee >= 0, "Fee {fee} must be non-negative");
102 use crate::constants::MAX_MONEY;
103 assert!(fee <= MAX_MONEY, "Fee {fee} must not exceed MAX_MONEY");
104 if !matches!(input_valid, ValidationResult::Valid) {
105 return Ok(MempoolResult::Rejected(
106 "Invalid transaction inputs".to_string(),
107 ));
108 }
109
110 if !is_coinbase(tx) {
112 let flags = calculate_script_flags(tx, witnesses);
115
116 #[cfg(all(feature = "production", feature = "rayon"))]
117 {
118 use rayon::prelude::*;
119
120 let input_utxos: Vec<(usize, Option<&UTXO>)> = {
124 let mut result = Vec::with_capacity(tx.inputs.len());
125 for (i, input) in tx.inputs.iter().enumerate() {
126 result.push((i, utxo_set.get(&input.prevout).map(|a| a.as_ref())));
127 }
128 result
129 };
130
131 let script_results: Result<Vec<bool>> = input_utxos
133 .par_iter()
134 .map(|(i, opt_utxo)| {
135 if let Some(utxo) = opt_utxo {
136 let input = &tx.inputs[*i];
137 let witness: Option<&ByteString> = witnesses
138 .and_then(|wits| wits.get(*i))
139 .and_then(|wit| wit.first());
140
141 verify_script(&input.script_sig, &utxo.script_pubkey, witness, flags)
142 } else {
143 Ok(false)
144 }
145 })
146 .collect();
147
148 let script_results = script_results?;
150 assert!(
152 script_results.len() == tx.inputs.len(),
153 "Script results count {} must match input count {}",
154 script_results.len(),
155 tx.inputs.len()
156 );
157 for (i, &is_valid) in script_results.iter().enumerate() {
158 assert!(
160 i < tx.inputs.len(),
161 "Input index {i} out of bounds in script validation loop",
162 );
163 if !is_valid {
165 return Ok(MempoolResult::Rejected(format!(
166 "Invalid script at input {i}"
167 )));
168 }
169 }
170 }
171
172 #[cfg(not(all(feature = "production", feature = "rayon")))]
173 {
174 for (i, input) in tx.inputs.iter().enumerate() {
176 if let Some(utxo) = utxo_set.get(&input.prevout) {
177 let witness: Option<&ByteString> =
182 witnesses.and_then(|wits| wits.get(i)).and_then(|wit| {
183 wit.first()
186 });
187
188 if !verify_script(&input.script_sig, &utxo.script_pubkey, witness, flags)? {
189 return Ok(MempoolResult::Rejected(format!(
190 "Invalid script at input {i}"
191 )));
192 }
193 }
194 }
195 }
196 }
197
198 if !check_mempool_rules(tx, fee, mempool)? {
200 return Ok(MempoolResult::Rejected("Failed mempool rules".to_string()));
201 }
202
203 if has_conflicts(tx, mempool)? {
205 return Ok(MempoolResult::Rejected(
206 "Transaction conflicts with mempool".to_string(),
207 ));
208 }
209
210 Ok(MempoolResult::Accepted)
211}
212
213fn calculate_script_flags(tx: &Transaction, witnesses: Option<&[Witness]>) -> u32 {
220 let has_witness = witnesses.map(|w| !w.is_empty()).unwrap_or(false);
231 const MEMPOOL_POLICY_HEIGHT: u64 = 1_000_000; crate::block::calculate_script_flags_for_block_network(
233 tx,
234 has_witness,
235 MEMPOOL_POLICY_HEIGHT,
236 crate::types::Network::Mainnet,
237 )
238}
239
240#[spec_locked("9.2")]
248pub fn is_standard_tx(tx: &Transaction) -> Result<bool> {
249 let tx_size = calculate_transaction_size(tx);
251 if tx_size > MAX_TX_SIZE {
252 return Ok(false);
253 }
254
255 for (i, input) in tx.inputs.iter().enumerate() {
257 assert!(i < tx.inputs.len(), "Input index {i} out of bounds");
259 assert!(
261 input.script_sig.len() <= MAX_SCRIPT_SIZE * 2,
262 "Script size {} must be reasonable for input {}",
263 input.script_sig.len(),
264 i
265 );
266 if input.script_sig.len() > MAX_SCRIPT_SIZE {
267 return Ok(false);
268 }
269 }
270
271 for (i, output) in tx.outputs.iter().enumerate() {
272 assert!(i < tx.outputs.len(), "Output index {i} out of bounds");
274 assert!(
276 output.script_pubkey.len() <= MAX_SCRIPT_SIZE * 2,
277 "Script size {} must be reasonable for output {}",
278 output.script_pubkey.len(),
279 i
280 );
281 if output.script_pubkey.len() > MAX_SCRIPT_SIZE {
282 return Ok(false);
283 }
284 }
285
286 for (i, output) in tx.outputs.iter().enumerate() {
288 assert!(
290 i < tx.outputs.len(),
291 "Output index {i} out of bounds in standard check"
292 );
293 if !is_standard_script(&output.script_pubkey)? {
294 return Ok(false);
295 }
296 }
297
298 let result = true;
300 Ok(result)
302}
303
304#[spec_locked("9.3")]
315pub fn replacement_checks(
316 new_tx: &Transaction,
317 existing_tx: &Transaction,
318 utxo_set: &UtxoSet,
319 mempool: &Mempool,
320) -> Result<bool> {
321 if new_tx.inputs.is_empty() && new_tx.outputs.is_empty() {
326 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
327 "New transaction must have at least one input or output"
328 .to_string()
329 .into(),
330 ));
331 }
332 if existing_tx.inputs.is_empty() && existing_tx.outputs.is_empty() {
333 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
334 "Existing transaction must have at least one input or output"
335 .to_string()
336 .into(),
337 ));
338 }
339 assert!(!is_coinbase(new_tx), "New transaction cannot be coinbase");
340 assert!(
341 !is_coinbase(existing_tx),
342 "Existing transaction cannot be coinbase"
343 );
344 assert!(
345 utxo_set.len() <= u32::MAX as usize,
346 "UTXO set size {} exceeds maximum",
347 utxo_set.len()
348 );
349
350 if !signals_rbf(existing_tx) {
353 return Ok(false);
354 }
355
356 let new_fee = calculate_fee(new_tx, utxo_set)?;
358 let existing_fee = calculate_fee(existing_tx, utxo_set)?;
359 assert!(new_fee >= 0, "New fee {new_fee} must be non-negative");
361 assert!(
362 existing_fee >= 0,
363 "Existing fee {existing_fee} must be non-negative"
364 );
365 use crate::constants::MAX_MONEY;
366 assert!(
367 new_fee <= MAX_MONEY,
368 "New fee {new_fee} must not exceed MAX_MONEY"
369 );
370 assert!(
371 existing_fee <= MAX_MONEY,
372 "Existing fee {existing_fee} must not exceed MAX_MONEY"
373 );
374
375 let new_tx_size = calculate_transaction_size_vbytes(new_tx);
376 let existing_tx_size = calculate_transaction_size_vbytes(existing_tx);
377 assert!(
379 new_tx_size > 0,
380 "New transaction size {new_tx_size} must be positive"
381 );
382 assert!(
383 existing_tx_size > 0,
384 "Existing transaction size {existing_tx_size} must be positive"
385 );
386 assert!(
387 new_tx_size <= MAX_TX_SIZE * 2,
388 "New transaction size {new_tx_size} must be reasonable"
389 );
390 assert!(
391 existing_tx_size <= MAX_TX_SIZE * 2,
392 "Existing transaction size {existing_tx_size} must be reasonable"
393 );
394
395 if new_tx_size == 0 || existing_tx_size == 0 {
396 return Ok(false);
397 }
398
399 debug_assert!(
406 new_tx_size > 0,
407 "New transaction size ({new_tx_size}) must be positive"
408 );
409 debug_assert!(
410 existing_tx_size > 0,
411 "Existing transaction size ({existing_tx_size}) must be positive"
412 );
413
414 let new_fee_scaled = (new_fee as u128)
417 .checked_mul(existing_tx_size as u128)
418 .ok_or_else(|| {
419 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
420 })?;
421 let existing_fee_scaled = (existing_fee as u128)
422 .checked_mul(new_tx_size as u128)
423 .ok_or_else(|| {
424 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
425 })?;
426
427 if new_fee_scaled <= existing_fee_scaled {
428 return Ok(false);
429 }
430
431 if new_fee <= existing_fee + MIN_RELAY_FEE {
433 return Ok(false);
434 }
435
436 if !has_conflict_with_tx(new_tx, existing_tx) {
438 return Ok(false);
439 }
440
441 if creates_new_dependencies(new_tx, existing_tx, utxo_set, mempool)? {
444 return Ok(false);
445 }
446
447 Ok(true)
448}
449
450pub type Mempool = HashSet<Hash>;
456
457#[derive(Debug, Clone, PartialEq, Eq)]
459pub enum MempoolResult {
460 Accepted,
461 Rejected(String),
462}
463
464#[spec_locked("9.1")]
525pub fn update_mempool_after_block(
526 mempool: &mut Mempool,
527 block: &crate::types::Block,
528 _utxo_set: &crate::types::UtxoSet,
529) -> Result<Vec<Hash>> {
530 let mut removed = Vec::new();
531
532 for tx in &block.transactions {
534 let tx_id = crate::block::calculate_tx_id(tx);
535 if mempool.remove(&tx_id) {
536 removed.push(tx_id);
537 }
538 }
539
540 Ok(removed)
548}
549
550#[spec_locked("9.1")]
565pub fn update_mempool_after_block_with_lookup<F>(
566 mempool: &mut Mempool,
567 block: &crate::types::Block,
568 get_tx_by_id: F,
569) -> Result<Vec<Hash>>
570where
571 F: Fn(&Hash) -> Option<crate::types::Transaction>,
572{
573 let mut removed = Vec::new();
574
575 for tx in &block.transactions {
577 let tx_id = crate::block::calculate_tx_id(tx);
578 if mempool.remove(&tx_id) {
579 removed.push(tx_id);
580 }
581 }
582
583 let mut spent_outpoints = std::collections::HashSet::new();
586 for tx in &block.transactions {
587 if !crate::transaction::is_coinbase(tx) {
588 for input in &tx.inputs {
589 spent_outpoints.insert(input.prevout);
590 }
591 }
592 }
593
594 let mut invalid_tx_ids = Vec::new();
596 for &tx_id in mempool.iter() {
597 if let Some(tx) = get_tx_by_id(&tx_id) {
598 for input in &tx.inputs {
600 if spent_outpoints.contains(&input.prevout) {
601 invalid_tx_ids.push(tx_id);
602 break;
603 }
604 }
605 }
606 }
607
608 for tx_id in invalid_tx_ids {
610 if mempool.remove(&tx_id) {
611 removed.push(tx_id);
612 }
613 }
614
615 Ok(removed)
616}
617
618fn check_mempool_rules(tx: &Transaction, fee: Integer, mempool: &Mempool) -> Result<bool> {
620 let tx_size = calculate_transaction_size(tx);
622 debug_assert!(
626 tx_size > 0,
627 "Transaction size ({tx_size}) must be positive for fee rate calculation"
628 );
629
630 let fee_rate = (fee as f64) / (tx_size as f64);
631
632 debug_assert!(
634 fee_rate >= 0.0,
635 "Fee rate ({fee_rate:.6}) must be non-negative (fee: {fee}, size: {tx_size})"
636 );
637
638 let config = crate::config::get_consensus_config_ref();
640 let min_fee_rate = config.mempool.min_relay_fee_rate as f64; let min_tx_fee = config.mempool.min_tx_fee; if fee < min_tx_fee {
645 return Ok(false);
646 }
647
648 if fee_rate < min_fee_rate {
650 return Ok(false);
651 }
652
653 if mempool.len() > config.mempool.max_mempool_txs {
656 return Ok(false);
657 }
658
659 Ok(true)
660}
661
662fn has_conflicts(tx: &Transaction, mempool: &Mempool) -> Result<bool> {
664 for input in &tx.inputs {
666 if mempool.contains(&input.prevout.hash) {
670 return Ok(true);
671 }
672 }
673
674 Ok(false)
675}
676
677#[spec_locked("9.1")]
718pub fn is_final_tx(tx: &Transaction, height: Natural, block_time: Natural) -> bool {
719 use crate::constants::SEQUENCE_FINAL;
720
721 if tx.lock_time == 0 {
723 return true;
724 }
725
726 let locktime_satisfied = if (tx.lock_time as u32) < LOCKTIME_THRESHOLD {
732 (tx.lock_time as Natural) < height
734 } else {
735 (tx.lock_time as Natural) < block_time
737 };
738
739 if locktime_satisfied {
740 return true;
741 }
742
743 for input in &tx.inputs {
747 if (input.sequence as u32) != SEQUENCE_FINAL {
748 return false;
749 }
750 }
751
752 true
754}
755
756#[spec_locked("9.3")]
760pub fn signals_rbf(tx: &Transaction) -> bool {
761 for input in &tx.inputs {
762 if (input.sequence as u32) < SEQUENCE_FINAL {
763 return true;
764 }
765 }
766 false
767}
768
769fn calculate_transaction_size_vbytes(tx: &Transaction) -> usize {
775 calculate_transaction_size(tx)
778}
779
780#[spec_locked("9.3")]
785pub fn has_conflict_with_tx(new_tx: &Transaction, existing_tx: &Transaction) -> bool {
786 for new_input in &new_tx.inputs {
787 for existing_input in &existing_tx.inputs {
788 if new_input.prevout == existing_input.prevout {
789 return true;
790 }
791 }
792 }
793 false
794}
795
796fn creates_new_dependencies(
802 new_tx: &Transaction,
803 existing_tx: &Transaction,
804 utxo_set: &UtxoSet,
805 mempool: &Mempool,
806) -> Result<bool> {
807 for input in &new_tx.inputs {
808 if utxo_set.contains_key(&input.prevout) {
810 continue;
811 }
812
813 let mut found_in_existing = false;
815 for existing_input in &existing_tx.inputs {
816 if existing_input.prevout == input.prevout {
817 found_in_existing = true;
818 break;
819 }
820 }
821
822 if found_in_existing {
823 continue;
824 }
825
826 if !mempool.contains(&input.prevout.hash) {
829 return Ok(true); }
831 }
832
833 Ok(false)
834}
835
836fn is_standard_script(script: &ByteString) -> Result<bool> {
838 if script.is_empty() {
841 return Ok(false);
842 }
843
844 if script.len() > MAX_SCRIPT_SIZE {
846 return Ok(false);
847 }
848
849 for &byte in script {
851 if byte > 0x60 && byte < 0x7f {
852 return Ok(false);
854 }
855 }
856
857 Ok(true)
858}
859
860#[deprecated(note = "Use crate::block::calculate_tx_id instead")]
865#[spec_locked("5.1")]
866pub fn calculate_tx_id(tx: &Transaction) -> Hash {
867 crate::block::calculate_tx_id(tx)
868}
869
870fn calculate_transaction_size(tx: &Transaction) -> usize {
874 use crate::transaction::calculate_transaction_size as tx_size;
875 tx_size(tx)
876}
877
878fn is_coinbase(tx: &Transaction) -> bool {
880 #[cfg(feature = "production")]
882 {
883 use crate::optimizations::constant_folding::is_zero_hash;
884 tx.inputs.len() == 1
885 && is_zero_hash(&tx.inputs[0].prevout.hash)
886 && tx.inputs[0].prevout.index == 0xffffffff
887 }
888
889 #[cfg(not(feature = "production"))]
890 {
891 tx.inputs.len() == 1
892 && tx.inputs[0].prevout.hash == [0u8; 32]
893 && tx.inputs[0].prevout.index == 0xffffffff
894 }
895}
896
897#[cfg(test)]
917mod tests {
918 use super::*;
919 use crate::opcodes::*;
920
921 #[test]
922 fn test_accept_to_memory_pool_valid() {
923 let tx = create_valid_transaction();
925 let utxo_set = create_test_utxo_set();
926 let mempool = Mempool::new();
927
928 let time_context = Some(TimeContext {
930 network_time: 1234567890,
931 median_time_past: 1234567890,
932 });
933 let result =
934 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
935 assert!(matches!(result, MempoolResult::Rejected(_)));
936 }
937
938 #[test]
939 fn test_accept_to_memory_pool_duplicate() {
940 let tx = create_valid_transaction();
941 let utxo_set = create_test_utxo_set();
942 let mut mempool = Mempool::new();
943 mempool.insert(crate::block::calculate_tx_id(&tx));
944
945 let time_context = Some(TimeContext {
946 network_time: 1234567890,
947 median_time_past: 1234567890,
948 });
949 let result =
950 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
951 assert!(matches!(result, MempoolResult::Rejected(_)));
952 }
953
954 #[test]
955 fn test_is_standard_tx_valid() {
956 let tx = create_valid_transaction();
957 assert!(is_standard_tx(&tx).unwrap());
958 }
959
960 #[test]
961 fn test_is_standard_tx_too_large() {
962 let mut tx = create_valid_transaction();
963 for _ in 0..MAX_INPUTS {
966 tx.inputs.push(create_dummy_input());
967 }
968 assert!(!is_standard_tx(&tx).unwrap());
970 }
971
972 #[test]
973 fn test_replacement_checks_all_requirements() {
974 let utxo_set = create_test_utxo_set();
975 let mempool = Mempool::new();
976
977 let mut existing_tx = create_valid_transaction();
979 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
980 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
987 new_tx.outputs[0].value = 8000; new_tx.outputs[0].value = 7999; let result = replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
993 assert!(result, "Valid RBF replacement should be accepted");
994 }
995
996 #[test]
997 fn test_replacement_checks_no_rbf_signal() {
998 let utxo_set = create_test_utxo_set();
999 let mempool = Mempool::new();
1000
1001 let new_tx = create_valid_transaction();
1002 let existing_tx = create_valid_transaction(); assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1006 }
1007
1008 #[test]
1009 fn test_replacement_checks_no_conflict() {
1010 let mut utxo_set = create_test_utxo_set();
1011 let new_outpoint = OutPoint {
1013 hash: [2; 32],
1014 index: 0,
1015 };
1016 let new_utxo = UTXO {
1017 value: 10000,
1018 script_pubkey: vec![OP_1].into(),
1019 height: 0,
1020 is_coinbase: false,
1021 };
1022 utxo_set.insert(new_outpoint, std::sync::Arc::new(new_utxo));
1023
1024 let mempool = Mempool::new();
1025
1026 let mut existing_tx = create_valid_transaction();
1027 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1028
1029 let mut new_tx = create_valid_transaction();
1031 new_tx.inputs[0].prevout.hash = [2; 32]; new_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1033 new_tx.outputs[0].value = 5000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1038 }
1039
1040 #[test]
1041 fn test_replacement_checks_fee_rate_too_low() {
1042 let utxo_set = create_test_utxo_set();
1043 let mempool = Mempool::new();
1044
1045 let mut existing_tx = create_valid_transaction();
1047 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1048 existing_tx.outputs[0].value = 5000; let mut new_tx = existing_tx.clone();
1052 new_tx.outputs[0].value = 4999; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1056 }
1057
1058 #[test]
1059 fn test_replacement_checks_absolute_fee_insufficient() {
1060 let utxo_set = create_test_utxo_set();
1061 let mempool = Mempool::new();
1062
1063 let mut existing_tx = create_valid_transaction();
1065 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1066 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1071 new_tx.outputs[0].value = 8001; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1075
1076 new_tx.outputs[0].value = 7999; }
1081
1082 #[test]
1087 fn test_accept_to_memory_pool_coinbase() {
1088 let coinbase_tx = create_coinbase_transaction();
1089 let utxo_set = UtxoSet::default();
1090 let mempool = Mempool::new();
1091 let time_context = Some(TimeContext {
1093 network_time: 0,
1094 median_time_past: 0,
1095 });
1096 let result =
1097 accept_to_memory_pool(&coinbase_tx, None, &utxo_set, &mempool, 100, time_context)
1098 .unwrap();
1099 assert!(matches!(result, MempoolResult::Rejected(_)));
1100 }
1101
1102 #[test]
1103 fn test_is_standard_tx_large_script() {
1104 let mut tx = create_valid_transaction();
1105 tx.inputs[0].script_sig = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1107
1108 let result = is_standard_tx(&tx).unwrap();
1109 assert!(!result);
1110 }
1111
1112 #[test]
1113 fn test_is_standard_tx_large_output_script() {
1114 let mut tx = create_valid_transaction();
1115 tx.outputs[0].script_pubkey = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1117
1118 let result = is_standard_tx(&tx).unwrap();
1119 assert!(!result);
1120 }
1121
1122 #[test]
1123 fn test_replacement_checks_new_unconfirmed_dependency() {
1124 let utxo_set = create_test_utxo_set();
1125 let mempool = Mempool::new();
1126
1127 let mut existing_tx = create_valid_transaction();
1129 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1130
1131 let mut new_tx = existing_tx.clone();
1133 new_tx.inputs.push(TransactionInput {
1134 prevout: OutPoint {
1135 hash: [99; 32],
1136 index: 0,
1137 }, script_sig: vec![],
1139 sequence: SEQUENCE_RBF as u64,
1140 });
1141 new_tx.outputs[0].value = 7000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1145 }
1146
1147 #[test]
1148 fn test_has_conflict_with_tx_true() {
1149 let tx1 = create_valid_transaction();
1150 let mut tx2 = create_valid_transaction();
1151 tx2.inputs[0].prevout = tx1.inputs[0].prevout.clone(); assert!(has_conflict_with_tx(&tx2, &tx1));
1154 }
1155
1156 #[test]
1157 fn test_has_conflict_with_tx_false() {
1158 let tx1 = create_valid_transaction();
1159 let mut tx2 = create_valid_transaction();
1160 tx2.inputs[0].prevout.hash = [2; 32]; assert!(!has_conflict_with_tx(&tx2, &tx1));
1163 }
1164
1165 #[test]
1166 fn test_replacement_checks_minimum_relay_fee() {
1167 let utxo_set = create_test_utxo_set();
1168 let mempool = Mempool::new();
1169
1170 let mut existing_tx = create_valid_transaction();
1172 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1173 existing_tx.outputs[0].value = 9500; let mut new_tx = existing_tx.clone();
1177 new_tx.outputs[0].value = 8500; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1179
1180 new_tx.outputs[0].value = 8499;
1184 }
1185
1186 #[test]
1187 fn test_check_mempool_rules_low_fee() {
1188 let tx = create_valid_transaction();
1189 let fee = 1; let mempool = Mempool::new();
1191
1192 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1193 assert!(!result);
1194 }
1195
1196 #[test]
1197 fn test_check_mempool_rules_high_fee() {
1198 let tx = create_valid_transaction();
1199 let fee = 10000; let mempool = Mempool::new();
1201
1202 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1203 assert!(result);
1204 }
1205
1206 #[test]
1207 fn test_check_mempool_rules_full_mempool() {
1208 let tx = create_valid_transaction();
1209 let fee = 10000;
1210 let mut mempool = Mempool::new();
1211
1212 for i in 0..100_001 {
1215 let mut hash = [0u8; 32];
1216 hash[0] = (i & 0xff) as u8;
1217 hash[1] = ((i >> 8) & 0xff) as u8;
1218 hash[2] = ((i >> 16) & 0xff) as u8;
1219 hash[3] = ((i >> 24) & 0xff) as u8;
1220 mempool.insert(hash);
1221 }
1222
1223 assert!(mempool.len() > 100_000);
1225
1226 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1227 assert!(!result);
1228 }
1229
1230 #[test]
1231 fn test_has_conflicts_no_conflicts() {
1232 let tx = create_valid_transaction();
1233 let mempool = Mempool::new();
1234
1235 let result = has_conflicts(&tx, &mempool).unwrap();
1236 assert!(!result);
1237 }
1238
1239 #[test]
1240 fn test_has_conflicts_with_conflicts() {
1241 let tx = create_valid_transaction();
1242 let mut mempool = Mempool::new();
1243
1244 mempool.insert(tx.inputs[0].prevout.hash);
1246
1247 let result = has_conflicts(&tx, &mempool).unwrap();
1248 assert!(result);
1249 }
1250
1251 #[test]
1252 fn test_signals_rbf_true() {
1253 let mut tx = create_valid_transaction();
1254 tx.inputs[0].sequence = 0xfffffffe; assert!(signals_rbf(&tx));
1257 }
1258
1259 #[test]
1260 fn test_signals_rbf_false() {
1261 let tx = create_valid_transaction(); assert!(!signals_rbf(&tx));
1264 }
1265
1266 #[test]
1267 fn test_calculate_fee_rate() {
1268 let tx = create_valid_transaction();
1269 let utxo_set = create_test_utxo_set();
1270 let fee = calculate_fee(&tx, &utxo_set);
1271
1272 assert!(fee.is_ok());
1274 }
1275
1276 #[test]
1277 fn test_creates_new_dependencies_no_new() {
1278 let new_tx = create_valid_transaction();
1279 let existing_tx = create_valid_transaction();
1280 let mempool = Mempool::new();
1281
1282 let utxo_set = create_test_utxo_set();
1283 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1284 assert!(!result);
1285 }
1286
1287 #[test]
1288 fn test_creates_new_dependencies_with_new() {
1289 let mut new_tx = create_valid_transaction();
1290 let existing_tx = create_valid_transaction();
1291 let mempool = Mempool::new();
1292
1293 new_tx.inputs[0].prevout.hash = [2; 32];
1295
1296 let utxo_set = create_test_utxo_set();
1297 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1298 assert!(result);
1299 }
1300
1301 #[test]
1302 fn test_is_standard_script_empty() {
1303 let script = vec![];
1304 let result = is_standard_script(&script).unwrap();
1305 assert!(!result);
1306 }
1307
1308 #[test]
1309 fn test_is_standard_script_too_large() {
1310 let script = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1311 let result = is_standard_script(&script).unwrap();
1312 assert!(!result);
1313 }
1314
1315 #[test]
1316 fn test_is_standard_script_non_standard_opcode() {
1317 let script = vec![OP_VERIF]; let result = is_standard_script(&script).unwrap();
1319 assert!(!result);
1320 }
1321
1322 #[test]
1323 fn test_is_standard_script_valid() {
1324 let script = vec![OP_1];
1325 let result = is_standard_script(&script).unwrap();
1326 assert!(result);
1327 }
1328
1329 #[test]
1330 fn test_calculate_tx_id() {
1331 let tx = create_valid_transaction();
1332 let tx_id = crate::block::calculate_tx_id(&tx);
1333
1334 assert_eq!(tx_id.len(), 32);
1336
1337 let tx_id2 = crate::block::calculate_tx_id(&tx);
1339 assert_eq!(tx_id, tx_id2);
1340 }
1341
1342 #[test]
1343 fn test_calculate_tx_id_different_txs() {
1344 let tx1 = create_valid_transaction();
1345 let mut tx2 = tx1.clone();
1346 tx2.version = 2; let id1 = crate::block::calculate_tx_id(&tx1);
1349 let id2 = crate::block::calculate_tx_id(&tx2);
1350
1351 assert_ne!(id1, id2);
1352 }
1353
1354 #[test]
1355 fn test_calculate_transaction_size() {
1356 let tx = create_valid_transaction();
1357 let size = calculate_transaction_size(&tx);
1358
1359 assert!(size > 0);
1360
1361 let size2 = calculate_transaction_size(&tx);
1363 assert_eq!(size, size2);
1364 }
1365
1366 #[test]
1367 fn test_calculate_transaction_size_multiple_inputs_outputs() {
1368 let mut tx = create_valid_transaction();
1369 tx.inputs.push(create_dummy_input());
1370 tx.outputs.push(create_dummy_output());
1371
1372 let size = calculate_transaction_size(&tx);
1373 assert!(size > 0);
1374 }
1375
1376 #[test]
1377 fn test_is_coinbase_true() {
1378 let coinbase_tx = create_coinbase_transaction();
1379 assert!(is_coinbase(&coinbase_tx));
1380 }
1381
1382 #[test]
1383 fn test_is_coinbase_false() {
1384 let regular_tx = create_valid_transaction();
1385 assert!(!is_coinbase(®ular_tx));
1386 }
1387
1388 fn create_valid_transaction() -> Transaction {
1390 Transaction {
1391 version: 1,
1392 inputs: vec![create_dummy_input()].into(),
1393 outputs: vec![create_dummy_output()].into(),
1394 lock_time: 0,
1395 }
1396 }
1397
1398 fn create_dummy_input() -> TransactionInput {
1399 TransactionInput {
1400 prevout: OutPoint {
1401 hash: [1; 32],
1402 index: 0,
1403 },
1404 script_sig: vec![OP_1],
1405 sequence: 0xffffffff,
1406 }
1407 }
1408
1409 fn create_dummy_output() -> TransactionOutput {
1410 TransactionOutput {
1411 value: 1000,
1412 script_pubkey: vec![OP_1].into(), }
1414 }
1415
1416 fn create_test_utxo_set() -> UtxoSet {
1417 let mut utxo_set = UtxoSet::default();
1418 let outpoint = OutPoint {
1419 hash: [1; 32],
1420 index: 0,
1421 };
1422 let utxo = UTXO {
1423 value: 10000,
1424 script_pubkey: vec![OP_1].into(), height: 0,
1426 is_coinbase: false,
1427 };
1428 utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
1429 utxo_set
1430 }
1431
1432 fn create_coinbase_transaction() -> Transaction {
1433 Transaction {
1434 version: 1,
1435 inputs: vec![TransactionInput {
1436 prevout: OutPoint {
1437 hash: [0; 32].into(),
1438 index: 0xffffffff,
1439 },
1440 script_sig: vec![],
1441 sequence: 0xffffffff,
1442 }]
1443 .into(),
1444 outputs: vec![TransactionOutput {
1445 value: 5000000000,
1446 script_pubkey: vec![].into(),
1447 }]
1448 .into(),
1449 lock_time: 0,
1450 }
1451 }
1452}