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 if is_coinbase(new_tx) {
340 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
341 "New transaction cannot be coinbase".to_string().into(),
342 ));
343 }
344 if is_coinbase(existing_tx) {
345 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
346 "Existing transaction cannot be coinbase".to_string().into(),
347 ));
348 }
349 assert!(
350 utxo_set.len() <= u32::MAX as usize,
351 "UTXO set size {} exceeds maximum",
352 utxo_set.len()
353 );
354
355 if !signals_rbf(existing_tx) {
358 return Ok(false);
359 }
360
361 let new_fee = calculate_fee(new_tx, utxo_set)?;
363 let existing_fee = calculate_fee(existing_tx, utxo_set)?;
364 assert!(new_fee >= 0, "New fee {new_fee} must be non-negative");
366 assert!(
367 existing_fee >= 0,
368 "Existing fee {existing_fee} must be non-negative"
369 );
370 use crate::constants::MAX_MONEY;
371 assert!(
372 new_fee <= MAX_MONEY,
373 "New fee {new_fee} must not exceed MAX_MONEY"
374 );
375 assert!(
376 existing_fee <= MAX_MONEY,
377 "Existing fee {existing_fee} must not exceed MAX_MONEY"
378 );
379
380 let new_tx_size = calculate_transaction_size_vbytes(new_tx);
381 let existing_tx_size = calculate_transaction_size_vbytes(existing_tx);
382 assert!(
384 new_tx_size > 0,
385 "New transaction size {new_tx_size} must be positive"
386 );
387 assert!(
388 existing_tx_size > 0,
389 "Existing transaction size {existing_tx_size} must be positive"
390 );
391 assert!(
392 new_tx_size <= MAX_TX_SIZE * 2,
393 "New transaction size {new_tx_size} must be reasonable"
394 );
395 assert!(
396 existing_tx_size <= MAX_TX_SIZE * 2,
397 "Existing transaction size {existing_tx_size} must be reasonable"
398 );
399
400 if new_tx_size == 0 || existing_tx_size == 0 {
401 return Ok(false);
402 }
403
404 debug_assert!(
411 new_tx_size > 0,
412 "New transaction size ({new_tx_size}) must be positive"
413 );
414 debug_assert!(
415 existing_tx_size > 0,
416 "Existing transaction size ({existing_tx_size}) must be positive"
417 );
418
419 let new_fee_scaled = (new_fee as u128)
422 .checked_mul(existing_tx_size as u128)
423 .ok_or_else(|| {
424 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
425 })?;
426 let existing_fee_scaled = (existing_fee as u128)
427 .checked_mul(new_tx_size as u128)
428 .ok_or_else(|| {
429 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
430 })?;
431
432 if new_fee_scaled <= existing_fee_scaled {
433 return Ok(false);
434 }
435
436 if new_fee <= existing_fee + MIN_RELAY_FEE {
438 return Ok(false);
439 }
440
441 if !has_conflict_with_tx(new_tx, existing_tx) {
443 return Ok(false);
444 }
445
446 if creates_new_dependencies(new_tx, existing_tx, utxo_set, mempool)? {
449 return Ok(false);
450 }
451
452 Ok(true)
453}
454
455pub type Mempool = HashSet<Hash>;
461
462#[derive(Debug, Clone, PartialEq, Eq)]
464pub enum MempoolResult {
465 Accepted,
466 Rejected(String),
467}
468
469#[spec_locked("9.1")]
530pub fn update_mempool_after_block(
531 mempool: &mut Mempool,
532 block: &crate::types::Block,
533 _utxo_set: &crate::types::UtxoSet,
534) -> Result<Vec<Hash>> {
535 let mut removed = Vec::new();
536
537 for tx in &block.transactions {
539 let tx_id = crate::block::calculate_tx_id(tx);
540 if mempool.remove(&tx_id) {
541 removed.push(tx_id);
542 }
543 }
544
545 Ok(removed)
553}
554
555#[spec_locked("9.1")]
570pub fn update_mempool_after_block_with_lookup<F>(
571 mempool: &mut Mempool,
572 block: &crate::types::Block,
573 get_tx_by_id: F,
574) -> Result<Vec<Hash>>
575where
576 F: Fn(&Hash) -> Option<crate::types::Transaction>,
577{
578 let mut removed = Vec::new();
579
580 for tx in &block.transactions {
582 let tx_id = crate::block::calculate_tx_id(tx);
583 if mempool.remove(&tx_id) {
584 removed.push(tx_id);
585 }
586 }
587
588 let mut spent_outpoints = std::collections::HashSet::new();
591 for tx in &block.transactions {
592 if !crate::transaction::is_coinbase(tx) {
593 for input in &tx.inputs {
594 spent_outpoints.insert(input.prevout);
595 }
596 }
597 }
598
599 let mut invalid_tx_ids = Vec::new();
601 for &tx_id in mempool.iter() {
602 if let Some(tx) = get_tx_by_id(&tx_id) {
603 for input in &tx.inputs {
605 if spent_outpoints.contains(&input.prevout) {
606 invalid_tx_ids.push(tx_id);
607 break;
608 }
609 }
610 }
611 }
612
613 for tx_id in invalid_tx_ids {
615 if mempool.remove(&tx_id) {
616 removed.push(tx_id);
617 }
618 }
619
620 Ok(removed)
621}
622
623fn check_mempool_rules(tx: &Transaction, fee: Integer, mempool: &Mempool) -> Result<bool> {
625 let tx_size = calculate_transaction_size(tx);
627 debug_assert!(
631 tx_size > 0,
632 "Transaction size ({tx_size}) must be positive for fee rate calculation"
633 );
634
635 let fee_rate = (fee as f64) / (tx_size as f64);
636
637 debug_assert!(
639 fee_rate >= 0.0,
640 "Fee rate ({fee_rate:.6}) must be non-negative (fee: {fee}, size: {tx_size})"
641 );
642
643 let config = crate::config::get_consensus_config_ref();
645 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 {
650 return Ok(false);
651 }
652
653 if fee_rate < min_fee_rate {
655 return Ok(false);
656 }
657
658 if mempool.len() > config.mempool.max_mempool_txs {
661 return Ok(false);
662 }
663
664 Ok(true)
665}
666
667fn has_conflicts(tx: &Transaction, mempool: &Mempool) -> Result<bool> {
669 for input in &tx.inputs {
671 if mempool.contains(&input.prevout.hash) {
675 return Ok(true);
676 }
677 }
678
679 Ok(false)
680}
681
682#[spec_locked("9.1")]
723pub fn is_final_tx(tx: &Transaction, height: Natural, block_time: Natural) -> bool {
724 use crate::constants::SEQUENCE_FINAL;
725
726 if tx.lock_time == 0 {
728 return true;
729 }
730
731 let locktime_satisfied = if (tx.lock_time as u32) < LOCKTIME_THRESHOLD {
737 (tx.lock_time as Natural) < height
739 } else {
740 (tx.lock_time as Natural) < block_time
742 };
743
744 if locktime_satisfied {
745 return true;
746 }
747
748 for input in &tx.inputs {
752 if (input.sequence as u32) != SEQUENCE_FINAL {
753 return false;
754 }
755 }
756
757 true
759}
760
761#[spec_locked("9.3")]
765pub fn signals_rbf(tx: &Transaction) -> bool {
766 for input in &tx.inputs {
767 if (input.sequence as u32) < SEQUENCE_FINAL {
768 return true;
769 }
770 }
771 false
772}
773
774fn calculate_transaction_size_vbytes(tx: &Transaction) -> usize {
780 calculate_transaction_size(tx)
783}
784
785#[spec_locked("9.3")]
790pub fn has_conflict_with_tx(new_tx: &Transaction, existing_tx: &Transaction) -> bool {
791 for new_input in &new_tx.inputs {
792 for existing_input in &existing_tx.inputs {
793 if new_input.prevout == existing_input.prevout {
794 return true;
795 }
796 }
797 }
798 false
799}
800
801fn creates_new_dependencies(
807 new_tx: &Transaction,
808 existing_tx: &Transaction,
809 utxo_set: &UtxoSet,
810 mempool: &Mempool,
811) -> Result<bool> {
812 for input in &new_tx.inputs {
813 if utxo_set.contains_key(&input.prevout) {
815 continue;
816 }
817
818 let mut found_in_existing = false;
820 for existing_input in &existing_tx.inputs {
821 if existing_input.prevout == input.prevout {
822 found_in_existing = true;
823 break;
824 }
825 }
826
827 if found_in_existing {
828 continue;
829 }
830
831 if !mempool.contains(&input.prevout.hash) {
834 return Ok(true); }
836 }
837
838 Ok(false)
839}
840
841fn is_standard_script(script: &ByteString) -> Result<bool> {
843 if script.is_empty() {
846 return Ok(false);
847 }
848
849 if script.len() > MAX_SCRIPT_SIZE {
851 return Ok(false);
852 }
853
854 for &byte in script {
856 if byte > 0x60 && byte < 0x7f {
857 return Ok(false);
859 }
860 }
861
862 Ok(true)
863}
864
865#[deprecated(note = "Use crate::block::calculate_tx_id instead")]
870#[spec_locked("5.1")]
871pub fn calculate_tx_id(tx: &Transaction) -> Hash {
872 crate::block::calculate_tx_id(tx)
873}
874
875fn calculate_transaction_size(tx: &Transaction) -> usize {
879 use crate::transaction::calculate_transaction_size as tx_size;
880 tx_size(tx)
881}
882
883fn is_coinbase(tx: &Transaction) -> bool {
885 #[cfg(feature = "production")]
887 {
888 use crate::optimizations::constant_folding::is_zero_hash;
889 tx.inputs.len() == 1
890 && is_zero_hash(&tx.inputs[0].prevout.hash)
891 && tx.inputs[0].prevout.index == 0xffffffff
892 }
893
894 #[cfg(not(feature = "production"))]
895 {
896 tx.inputs.len() == 1
897 && tx.inputs[0].prevout.hash == [0u8; 32]
898 && tx.inputs[0].prevout.index == 0xffffffff
899 }
900}
901
902#[cfg(test)]
922mod tests {
923 use super::*;
924 use crate::opcodes::*;
925
926 #[test]
927 fn test_accept_to_memory_pool_valid() {
928 let tx = create_valid_transaction();
930 let utxo_set = create_test_utxo_set();
931 let mempool = Mempool::new();
932
933 let time_context = Some(TimeContext {
935 network_time: 1234567890,
936 median_time_past: 1234567890,
937 });
938 let result =
939 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
940 assert!(matches!(result, MempoolResult::Rejected(_)));
941 }
942
943 #[test]
944 fn test_accept_to_memory_pool_duplicate() {
945 let tx = create_valid_transaction();
946 let utxo_set = create_test_utxo_set();
947 let mut mempool = Mempool::new();
948 mempool.insert(crate::block::calculate_tx_id(&tx));
949
950 let time_context = Some(TimeContext {
951 network_time: 1234567890,
952 median_time_past: 1234567890,
953 });
954 let result =
955 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
956 assert!(matches!(result, MempoolResult::Rejected(_)));
957 }
958
959 #[test]
960 fn test_is_standard_tx_valid() {
961 let tx = create_valid_transaction();
962 assert!(is_standard_tx(&tx).unwrap());
963 }
964
965 #[test]
966 fn test_is_standard_tx_too_large() {
967 let mut tx = create_valid_transaction();
968 for _ in 0..MAX_INPUTS {
971 tx.inputs.push(create_dummy_input());
972 }
973 assert!(!is_standard_tx(&tx).unwrap());
975 }
976
977 #[test]
978 fn test_replacement_checks_all_requirements() {
979 let utxo_set = create_test_utxo_set();
980 let mempool = Mempool::new();
981
982 let mut existing_tx = create_valid_transaction();
984 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
985 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
992 new_tx.outputs[0].value = 8000; new_tx.outputs[0].value = 7999; let result = replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
998 assert!(result, "Valid RBF replacement should be accepted");
999 }
1000
1001 #[test]
1002 fn test_replacement_checks_no_rbf_signal() {
1003 let utxo_set = create_test_utxo_set();
1004 let mempool = Mempool::new();
1005
1006 let new_tx = create_valid_transaction();
1007 let existing_tx = create_valid_transaction(); assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1011 }
1012
1013 #[test]
1014 fn test_replacement_checks_no_conflict() {
1015 let mut utxo_set = create_test_utxo_set();
1016 let new_outpoint = OutPoint {
1018 hash: [2; 32],
1019 index: 0,
1020 };
1021 let new_utxo = UTXO {
1022 value: 10000,
1023 script_pubkey: vec![OP_1].into(),
1024 height: 0,
1025 is_coinbase: false,
1026 };
1027 utxo_set.insert(new_outpoint, std::sync::Arc::new(new_utxo));
1028
1029 let mempool = Mempool::new();
1030
1031 let mut existing_tx = create_valid_transaction();
1032 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1033
1034 let mut new_tx = create_valid_transaction();
1036 new_tx.inputs[0].prevout.hash = [2; 32]; new_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1038 new_tx.outputs[0].value = 5000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1043 }
1044
1045 #[test]
1046 fn test_replacement_checks_fee_rate_too_low() {
1047 let utxo_set = create_test_utxo_set();
1048 let mempool = Mempool::new();
1049
1050 let mut existing_tx = create_valid_transaction();
1052 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1053 existing_tx.outputs[0].value = 5000; let mut new_tx = existing_tx.clone();
1057 new_tx.outputs[0].value = 4999; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1061 }
1062
1063 #[test]
1064 fn test_replacement_checks_absolute_fee_insufficient() {
1065 let utxo_set = create_test_utxo_set();
1066 let mempool = Mempool::new();
1067
1068 let mut existing_tx = create_valid_transaction();
1070 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1071 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1076 new_tx.outputs[0].value = 8001; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1080
1081 new_tx.outputs[0].value = 7999; }
1086
1087 #[test]
1092 fn test_accept_to_memory_pool_coinbase() {
1093 let coinbase_tx = create_coinbase_transaction();
1094 let utxo_set = UtxoSet::default();
1095 let mempool = Mempool::new();
1096 let time_context = Some(TimeContext {
1098 network_time: 0,
1099 median_time_past: 0,
1100 });
1101 let result =
1102 accept_to_memory_pool(&coinbase_tx, None, &utxo_set, &mempool, 100, time_context)
1103 .unwrap();
1104 assert!(matches!(result, MempoolResult::Rejected(_)));
1105 }
1106
1107 #[test]
1108 fn test_is_standard_tx_large_script() {
1109 let mut tx = create_valid_transaction();
1110 tx.inputs[0].script_sig = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1112
1113 let result = is_standard_tx(&tx).unwrap();
1114 assert!(!result);
1115 }
1116
1117 #[test]
1118 fn test_is_standard_tx_large_output_script() {
1119 let mut tx = create_valid_transaction();
1120 tx.outputs[0].script_pubkey = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1122
1123 let result = is_standard_tx(&tx).unwrap();
1124 assert!(!result);
1125 }
1126
1127 #[test]
1128 fn test_replacement_checks_new_unconfirmed_dependency() {
1129 let utxo_set = create_test_utxo_set();
1130 let mempool = Mempool::new();
1131
1132 let mut existing_tx = create_valid_transaction();
1134 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1135
1136 let mut new_tx = existing_tx.clone();
1138 new_tx.inputs.push(TransactionInput {
1139 prevout: OutPoint {
1140 hash: [99; 32],
1141 index: 0,
1142 }, script_sig: vec![],
1144 sequence: SEQUENCE_RBF as u64,
1145 });
1146 new_tx.outputs[0].value = 7000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1150 }
1151
1152 #[test]
1153 fn test_has_conflict_with_tx_true() {
1154 let tx1 = create_valid_transaction();
1155 let mut tx2 = create_valid_transaction();
1156 tx2.inputs[0].prevout = tx1.inputs[0].prevout.clone(); assert!(has_conflict_with_tx(&tx2, &tx1));
1159 }
1160
1161 #[test]
1162 fn test_has_conflict_with_tx_false() {
1163 let tx1 = create_valid_transaction();
1164 let mut tx2 = create_valid_transaction();
1165 tx2.inputs[0].prevout.hash = [2; 32]; assert!(!has_conflict_with_tx(&tx2, &tx1));
1168 }
1169
1170 #[test]
1171 fn test_replacement_checks_minimum_relay_fee() {
1172 let utxo_set = create_test_utxo_set();
1173 let mempool = Mempool::new();
1174
1175 let mut existing_tx = create_valid_transaction();
1177 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1178 existing_tx.outputs[0].value = 9500; let mut new_tx = existing_tx.clone();
1182 new_tx.outputs[0].value = 8500; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1184
1185 new_tx.outputs[0].value = 8499;
1189 }
1190
1191 #[test]
1192 fn test_check_mempool_rules_low_fee() {
1193 let tx = create_valid_transaction();
1194 let fee = 1; let mempool = Mempool::new();
1196
1197 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1198 assert!(!result);
1199 }
1200
1201 #[test]
1202 fn test_check_mempool_rules_high_fee() {
1203 let tx = create_valid_transaction();
1204 let fee = 10000; let mempool = Mempool::new();
1206
1207 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1208 assert!(result);
1209 }
1210
1211 #[test]
1212 fn test_check_mempool_rules_full_mempool() {
1213 let tx = create_valid_transaction();
1214 let fee = 10000;
1215 let mut mempool = Mempool::new();
1216
1217 for i in 0..100_001 {
1220 let mut hash = [0u8; 32];
1221 hash[0] = (i & 0xff) as u8;
1222 hash[1] = ((i >> 8) & 0xff) as u8;
1223 hash[2] = ((i >> 16) & 0xff) as u8;
1224 hash[3] = ((i >> 24) & 0xff) as u8;
1225 mempool.insert(hash);
1226 }
1227
1228 assert!(mempool.len() > 100_000);
1230
1231 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1232 assert!(!result);
1233 }
1234
1235 #[test]
1236 fn test_has_conflicts_no_conflicts() {
1237 let tx = create_valid_transaction();
1238 let mempool = Mempool::new();
1239
1240 let result = has_conflicts(&tx, &mempool).unwrap();
1241 assert!(!result);
1242 }
1243
1244 #[test]
1245 fn test_has_conflicts_with_conflicts() {
1246 let tx = create_valid_transaction();
1247 let mut mempool = Mempool::new();
1248
1249 mempool.insert(tx.inputs[0].prevout.hash);
1251
1252 let result = has_conflicts(&tx, &mempool).unwrap();
1253 assert!(result);
1254 }
1255
1256 #[test]
1257 fn test_signals_rbf_true() {
1258 let mut tx = create_valid_transaction();
1259 tx.inputs[0].sequence = 0xfffffffe; assert!(signals_rbf(&tx));
1262 }
1263
1264 #[test]
1265 fn test_signals_rbf_false() {
1266 let tx = create_valid_transaction(); assert!(!signals_rbf(&tx));
1269 }
1270
1271 #[test]
1272 fn test_calculate_fee_rate() {
1273 let tx = create_valid_transaction();
1274 let utxo_set = create_test_utxo_set();
1275 let fee = calculate_fee(&tx, &utxo_set);
1276
1277 assert!(fee.is_ok());
1279 }
1280
1281 #[test]
1282 fn test_creates_new_dependencies_no_new() {
1283 let new_tx = create_valid_transaction();
1284 let existing_tx = create_valid_transaction();
1285 let mempool = Mempool::new();
1286
1287 let utxo_set = create_test_utxo_set();
1288 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1289 assert!(!result);
1290 }
1291
1292 #[test]
1293 fn test_creates_new_dependencies_with_new() {
1294 let mut new_tx = create_valid_transaction();
1295 let existing_tx = create_valid_transaction();
1296 let mempool = Mempool::new();
1297
1298 new_tx.inputs[0].prevout.hash = [2; 32];
1300
1301 let utxo_set = create_test_utxo_set();
1302 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1303 assert!(result);
1304 }
1305
1306 #[test]
1307 fn test_is_standard_script_empty() {
1308 let script = vec![];
1309 let result = is_standard_script(&script).unwrap();
1310 assert!(!result);
1311 }
1312
1313 #[test]
1314 fn test_is_standard_script_too_large() {
1315 let script = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1316 let result = is_standard_script(&script).unwrap();
1317 assert!(!result);
1318 }
1319
1320 #[test]
1321 fn test_is_standard_script_non_standard_opcode() {
1322 let script = vec![OP_VERIF]; let result = is_standard_script(&script).unwrap();
1324 assert!(!result);
1325 }
1326
1327 #[test]
1328 fn test_is_standard_script_valid() {
1329 let script = vec![OP_1];
1330 let result = is_standard_script(&script).unwrap();
1331 assert!(result);
1332 }
1333
1334 #[test]
1335 fn test_calculate_tx_id() {
1336 let tx = create_valid_transaction();
1337 let tx_id = crate::block::calculate_tx_id(&tx);
1338
1339 assert_eq!(tx_id.len(), 32);
1341
1342 let tx_id2 = crate::block::calculate_tx_id(&tx);
1344 assert_eq!(tx_id, tx_id2);
1345 }
1346
1347 #[test]
1348 fn test_calculate_tx_id_different_txs() {
1349 let tx1 = create_valid_transaction();
1350 let mut tx2 = tx1.clone();
1351 tx2.version = 2; let id1 = crate::block::calculate_tx_id(&tx1);
1354 let id2 = crate::block::calculate_tx_id(&tx2);
1355
1356 assert_ne!(id1, id2);
1357 }
1358
1359 #[test]
1360 fn test_calculate_transaction_size() {
1361 let tx = create_valid_transaction();
1362 let size = calculate_transaction_size(&tx);
1363
1364 assert!(size > 0);
1365
1366 let size2 = calculate_transaction_size(&tx);
1368 assert_eq!(size, size2);
1369 }
1370
1371 #[test]
1372 fn test_calculate_transaction_size_multiple_inputs_outputs() {
1373 let mut tx = create_valid_transaction();
1374 tx.inputs.push(create_dummy_input());
1375 tx.outputs.push(create_dummy_output());
1376
1377 let size = calculate_transaction_size(&tx);
1378 assert!(size > 0);
1379 }
1380
1381 #[test]
1382 fn test_is_coinbase_true() {
1383 let coinbase_tx = create_coinbase_transaction();
1384 assert!(is_coinbase(&coinbase_tx));
1385 }
1386
1387 #[test]
1388 fn test_is_coinbase_false() {
1389 let regular_tx = create_valid_transaction();
1390 assert!(!is_coinbase(®ular_tx));
1391 }
1392
1393 fn create_valid_transaction() -> Transaction {
1395 Transaction {
1396 version: 1,
1397 inputs: vec![create_dummy_input()].into(),
1398 outputs: vec![create_dummy_output()].into(),
1399 lock_time: 0,
1400 }
1401 }
1402
1403 fn create_dummy_input() -> TransactionInput {
1404 TransactionInput {
1405 prevout: OutPoint {
1406 hash: [1; 32],
1407 index: 0,
1408 },
1409 script_sig: vec![OP_1],
1410 sequence: 0xffffffff,
1411 }
1412 }
1413
1414 fn create_dummy_output() -> TransactionOutput {
1415 TransactionOutput {
1416 value: 1000,
1417 script_pubkey: vec![OP_1].into(), }
1419 }
1420
1421 fn create_test_utxo_set() -> UtxoSet {
1422 let mut utxo_set = UtxoSet::default();
1423 let outpoint = OutPoint {
1424 hash: [1; 32],
1425 index: 0,
1426 };
1427 let utxo = UTXO {
1428 value: 10000,
1429 script_pubkey: vec![OP_1].into(), height: 0,
1431 is_coinbase: false,
1432 };
1433 utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
1434 utxo_set
1435 }
1436
1437 fn create_coinbase_transaction() -> Transaction {
1438 Transaction {
1439 version: 1,
1440 inputs: vec![TransactionInput {
1441 prevout: OutPoint {
1442 hash: [0; 32].into(),
1443 index: 0xffffffff,
1444 },
1445 script_sig: vec![],
1446 sequence: 0xffffffff,
1447 }]
1448 .into(),
1449 outputs: vec![TransactionOutput {
1450 value: 5000000000,
1451 script_pubkey: vec![].into(),
1452 }]
1453 .into(),
1454 lock_time: 0,
1455 }
1456 }
1457}