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", "AcceptToMemoryPool")]
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", "IsStandardTxWithConfig")]
246pub fn is_standard_tx_with_config(
247 tx: &crate::types::Transaction,
248 config: Option<&blvm_primitives::config::MempoolConfig>,
249) -> Result<bool> {
250 let Some(cfg) = config else {
251 return is_standard_tx(tx);
252 };
253
254 let tx_size = crate::transaction::calculate_transaction_size(tx);
256 if tx_size > MAX_TX_SIZE {
257 return Ok(false);
258 }
259 for input in tx.inputs.iter() {
260 if input.script_sig.len() > MAX_SCRIPT_SIZE {
261 return Ok(false);
262 }
263 }
264
265 let max_script = cfg.max_standard_script_size as usize;
266 let max_op_return = cfg.max_op_return_size as usize;
267 let reject_envelope = cfg.reject_envelope_protocol;
268 let reject_multi_op_return = cfg.reject_multiple_op_return;
269
270 let mut op_return_count = 0usize;
271 for output in tx.outputs.iter() {
272 let s = &output.script_pubkey;
273
274 if s.len() > max_script {
276 return Ok(false);
277 }
278
279 if s.first() == Some(&0x6a) {
280 op_return_count += 1;
282 if s.len().saturating_sub(1) > max_op_return {
283 return Ok(false);
284 }
285 } else if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
286 if reject_envelope {
288 return Ok(false);
289 }
290 }
291 }
292
293 if reject_multi_op_return && op_return_count > 1 {
295 return Ok(false);
296 }
297
298 Ok(true)
299}
300
301#[spec_locked("9.2", "IsStandardTx")]
309pub fn is_standard_tx(tx: &Transaction) -> Result<bool> {
310 let tx_size = calculate_transaction_size(tx);
312 if tx_size > MAX_TX_SIZE {
313 return Ok(false);
314 }
315
316 for (i, input) in tx.inputs.iter().enumerate() {
318 assert!(i < tx.inputs.len(), "Input index {i} out of bounds");
320 assert!(
322 input.script_sig.len() <= MAX_SCRIPT_SIZE * 2,
323 "Script size {} must be reasonable for input {}",
324 input.script_sig.len(),
325 i
326 );
327 if input.script_sig.len() > MAX_SCRIPT_SIZE {
328 return Ok(false);
329 }
330 }
331
332 for (i, output) in tx.outputs.iter().enumerate() {
333 assert!(i < tx.outputs.len(), "Output index {i} out of bounds");
335 assert!(
337 output.script_pubkey.len() <= MAX_SCRIPT_SIZE * 2,
338 "Script size {} must be reasonable for output {}",
339 output.script_pubkey.len(),
340 i
341 );
342 if output.script_pubkey.len() > MAX_SCRIPT_SIZE {
343 return Ok(false);
344 }
345 }
346
347 let mut op_return_count = 0usize;
349 for (i, output) in tx.outputs.iter().enumerate() {
350 assert!(
352 i < tx.outputs.len(),
353 "Output index {i} out of bounds in standard check"
354 );
355 if !is_standard_script(&output.script_pubkey)? {
356 return Ok(false);
357 }
358
359 if output.script_pubkey.first() == Some(&0x6a) {
361 op_return_count += 1;
362 }
363
364 let s = &output.script_pubkey;
366 if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
367 return Ok(false);
368 }
369 }
370
371 if op_return_count > 1 {
373 return Ok(false);
374 }
375
376 Ok(true)
377}
378
379#[spec_locked("9.3", "ReplacementChecks")]
390pub fn replacement_checks(
391 new_tx: &Transaction,
392 existing_tx: &Transaction,
393 utxo_set: &UtxoSet,
394 mempool: &Mempool,
395) -> Result<bool> {
396 if new_tx.inputs.is_empty() && new_tx.outputs.is_empty() {
401 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
402 "New transaction must have at least one input or output"
403 .to_string()
404 .into(),
405 ));
406 }
407 if existing_tx.inputs.is_empty() && existing_tx.outputs.is_empty() {
408 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
409 "Existing transaction must have at least one input or output"
410 .to_string()
411 .into(),
412 ));
413 }
414 if is_coinbase(new_tx) {
415 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
416 "New transaction cannot be coinbase".to_string().into(),
417 ));
418 }
419 if is_coinbase(existing_tx) {
420 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
421 "Existing transaction cannot be coinbase".to_string().into(),
422 ));
423 }
424 assert!(
425 utxo_set.len() <= u32::MAX as usize,
426 "UTXO set size {} exceeds maximum",
427 utxo_set.len()
428 );
429
430 if !signals_rbf(existing_tx) {
433 return Ok(false);
434 }
435
436 let new_fee = calculate_fee(new_tx, utxo_set)?;
438 let existing_fee = calculate_fee(existing_tx, utxo_set)?;
439 assert!(new_fee >= 0, "New fee {new_fee} must be non-negative");
441 assert!(
442 existing_fee >= 0,
443 "Existing fee {existing_fee} must be non-negative"
444 );
445 use crate::constants::MAX_MONEY;
446 assert!(
447 new_fee <= MAX_MONEY,
448 "New fee {new_fee} must not exceed MAX_MONEY"
449 );
450 assert!(
451 existing_fee <= MAX_MONEY,
452 "Existing fee {existing_fee} must not exceed MAX_MONEY"
453 );
454
455 let new_tx_size = calculate_transaction_size_vbytes(new_tx);
456 let existing_tx_size = calculate_transaction_size_vbytes(existing_tx);
457 assert!(
459 new_tx_size > 0,
460 "New transaction size {new_tx_size} must be positive"
461 );
462 assert!(
463 existing_tx_size > 0,
464 "Existing transaction size {existing_tx_size} must be positive"
465 );
466 assert!(
467 new_tx_size <= MAX_TX_SIZE * 2,
468 "New transaction size {new_tx_size} must be reasonable"
469 );
470 assert!(
471 existing_tx_size <= MAX_TX_SIZE * 2,
472 "Existing transaction size {existing_tx_size} must be reasonable"
473 );
474
475 if new_tx_size == 0 || existing_tx_size == 0 {
476 return Ok(false);
477 }
478
479 debug_assert!(
486 new_tx_size > 0,
487 "New transaction size ({new_tx_size}) must be positive"
488 );
489 debug_assert!(
490 existing_tx_size > 0,
491 "Existing transaction size ({existing_tx_size}) must be positive"
492 );
493
494 let new_fee_scaled = (new_fee as u128)
497 .checked_mul(existing_tx_size as u128)
498 .ok_or_else(|| {
499 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
500 })?;
501 let existing_fee_scaled = (existing_fee as u128)
502 .checked_mul(new_tx_size as u128)
503 .ok_or_else(|| {
504 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
505 })?;
506
507 if new_fee_scaled <= existing_fee_scaled {
508 return Ok(false);
509 }
510
511 if new_fee <= existing_fee + MIN_RELAY_FEE {
513 return Ok(false);
514 }
515
516 if !has_conflict_with_tx(new_tx, existing_tx) {
518 return Ok(false);
519 }
520
521 if creates_new_dependencies(new_tx, existing_tx, utxo_set, mempool)? {
524 return Ok(false);
525 }
526
527 Ok(true)
528}
529
530pub type Mempool = HashSet<Hash>;
536
537#[derive(Debug, Clone, PartialEq, Eq)]
539pub enum MempoolResult {
540 Accepted,
541 Rejected(String),
542}
543
544pub fn update_mempool_after_block(
605 mempool: &mut Mempool,
606 block: &crate::types::Block,
607 _utxo_set: &crate::types::UtxoSet,
608) -> Result<Vec<Hash>> {
609 let mut removed = Vec::new();
610
611 for tx in &block.transactions {
613 let tx_id = crate::block::calculate_tx_id(tx);
614 if mempool.remove(&tx_id) {
615 removed.push(tx_id);
616 }
617 }
618
619 Ok(removed)
627}
628
629pub fn update_mempool_after_block_with_lookup<F>(
644 mempool: &mut Mempool,
645 block: &crate::types::Block,
646 get_tx_by_id: F,
647) -> Result<Vec<Hash>>
648where
649 F: Fn(&Hash) -> Option<crate::types::Transaction>,
650{
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_outpoints = std::collections::HashSet::new();
664 for tx in &block.transactions {
665 if !crate::transaction::is_coinbase(tx) {
666 for input in &tx.inputs {
667 spent_outpoints.insert(input.prevout);
668 }
669 }
670 }
671
672 let mut invalid_tx_ids = Vec::new();
674 for &tx_id in mempool.iter() {
675 if let Some(tx) = get_tx_by_id(&tx_id) {
676 for input in &tx.inputs {
678 if spent_outpoints.contains(&input.prevout) {
679 invalid_tx_ids.push(tx_id);
680 break;
681 }
682 }
683 }
684 }
685
686 for tx_id in invalid_tx_ids {
688 if mempool.remove(&tx_id) {
689 removed.push(tx_id);
690 }
691 }
692
693 Ok(removed)
694}
695
696#[spec_locked("9.1", "CheckMempoolRules")]
698pub(crate) fn check_mempool_rules(
699 tx: &Transaction,
700 fee: Integer,
701 mempool: &Mempool,
702) -> Result<bool> {
703 let tx_size = calculate_transaction_size(tx);
705 debug_assert!(
709 tx_size > 0,
710 "Transaction size ({tx_size}) must be positive for fee rate calculation"
711 );
712
713 let fee_rate = (fee as f64) / (tx_size as f64);
714
715 debug_assert!(
717 fee_rate >= 0.0,
718 "Fee rate ({fee_rate:.6}) must be non-negative (fee: {fee}, size: {tx_size})"
719 );
720
721 let config = crate::config::get_consensus_config_ref();
723 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 {
728 return Ok(false);
729 }
730
731 if fee_rate < min_fee_rate {
733 return Ok(false);
734 }
735
736 if mempool.len() > config.mempool.max_mempool_txs {
739 return Ok(false);
740 }
741
742 Ok(true)
743}
744
745fn has_conflicts(tx: &Transaction, mempool: &Mempool) -> Result<bool> {
747 for input in &tx.inputs {
749 if mempool.contains(&input.prevout.hash) {
753 return Ok(true);
754 }
755 }
756
757 Ok(false)
758}
759
760#[spec_locked("9.1.1", "CheckFinalTxAtTip")]
801pub fn is_final_tx(tx: &Transaction, height: Natural, block_time: Natural) -> bool {
802 use crate::constants::SEQUENCE_FINAL;
803
804 if tx.lock_time == 0 {
806 return true;
807 }
808
809 let locktime_satisfied = if (tx.lock_time as u32) < LOCKTIME_THRESHOLD {
815 (tx.lock_time as Natural) < height
817 } else {
818 (tx.lock_time as Natural) < block_time
820 };
821
822 if locktime_satisfied {
823 return true;
824 }
825
826 for input in &tx.inputs {
830 if (input.sequence as u32) != SEQUENCE_FINAL {
831 return false;
832 }
833 }
834
835 true
837}
838
839#[spec_locked("9.3", "SignalsRBF")]
843pub fn signals_rbf(tx: &Transaction) -> bool {
844 for input in &tx.inputs {
845 if (input.sequence as u32) < SEQUENCE_FINAL {
846 return true;
847 }
848 }
849 false
850}
851
852fn calculate_transaction_size_vbytes(tx: &Transaction) -> usize {
858 calculate_transaction_size(tx)
861}
862
863#[spec_locked("9.3", "HasConflictWithTx")]
868pub fn has_conflict_with_tx(new_tx: &Transaction, existing_tx: &Transaction) -> bool {
869 for new_input in &new_tx.inputs {
870 for existing_input in &existing_tx.inputs {
871 if new_input.prevout == existing_input.prevout {
872 return true;
873 }
874 }
875 }
876 false
877}
878
879#[spec_locked("9.3", "CreatesNewDependencies")]
885pub(crate) fn creates_new_dependencies(
886 new_tx: &Transaction,
887 existing_tx: &Transaction,
888 utxo_set: &UtxoSet,
889 mempool: &Mempool,
890) -> Result<bool> {
891 for input in &new_tx.inputs {
892 if utxo_set.contains_key(&input.prevout) {
894 continue;
895 }
896
897 let mut found_in_existing = false;
899 for existing_input in &existing_tx.inputs {
900 if existing_input.prevout == input.prevout {
901 found_in_existing = true;
902 break;
903 }
904 }
905
906 if found_in_existing {
907 continue;
908 }
909
910 if !mempool.contains(&input.prevout.hash) {
913 return Ok(true); }
915 }
916
917 Ok(false)
918}
919
920fn script_opcode_advance(script: &[u8], pc: usize) -> usize {
922 let opcode = script[pc];
923 match opcode {
924 0x01..=0x4b => 1 + opcode as usize,
925 0x4c => {
926 if pc + 1 < script.len() {
927 2 + script[pc + 1] as usize
928 } else {
929 1
930 }
931 }
932 0x4d => {
933 if pc + 2 < script.len() {
934 3 + u16::from_le_bytes([script[pc + 1], script[pc + 2]]) as usize
935 } else {
936 1
937 }
938 }
939 0x4e => {
940 if pc + 4 < script.len() {
941 5 + u32::from_le_bytes([
942 script[pc + 1],
943 script[pc + 2],
944 script[pc + 3],
945 script[pc + 4],
946 ]) as usize
947 } else {
948 1
949 }
950 }
951 _ => 1,
952 }
953}
954
955#[inline]
957fn is_disabled_policy_opcode(opcode: u8) -> bool {
958 use crate::opcodes::{
959 OP_2DIV, OP_2MUL, OP_AND, OP_CAT, OP_DIV, OP_INVERT, OP_LEFT, OP_LSHIFT, OP_MOD, OP_MUL,
960 OP_OR, OP_RIGHT, OP_RSHIFT, OP_SUBSTR, OP_VER, OP_VERIF, OP_VERNOTIF, OP_XOR,
961 };
962 matches!(
963 opcode,
964 OP_VER
965 | OP_VERIF
966 | OP_VERNOTIF
967 | OP_CAT
968 | OP_SUBSTR
969 | OP_LEFT
970 | OP_RIGHT
971 | OP_INVERT
972 | OP_AND
973 | OP_OR
974 | OP_XOR
975 | OP_2MUL
976 | OP_2DIV
977 | OP_MUL
978 | OP_DIV
979 | OP_MOD
980 | OP_LSHIFT
981 | OP_RSHIFT
982 )
983}
984
985#[spec_locked("9.1", "IsStandardScript")]
987pub(crate) fn is_standard_script(script: &ByteString) -> Result<bool> {
988 if script.is_empty() {
989 return Ok(false);
990 }
991
992 if script.len() > MAX_SCRIPT_SIZE {
993 return Ok(false);
994 }
995
996 if script[0] == 0x6a {
999 return Ok(script.len() <= 83);
1001 }
1002
1003 let mut pc = 0;
1005 while pc < script.len() {
1006 let opcode = script[pc];
1007 if is_disabled_policy_opcode(opcode) {
1008 return Ok(false);
1009 }
1010 let advance = script_opcode_advance(script, pc);
1011 if advance == 0 {
1012 break;
1013 }
1014 pc += advance;
1015 }
1016
1017 Ok(true)
1018}
1019
1020#[deprecated(note = "Use crate::block::calculate_tx_id instead")]
1025#[spec_locked("5.1", "CalculateTxId")]
1026pub fn calculate_tx_id(tx: &Transaction) -> Hash {
1027 crate::block::calculate_tx_id(tx)
1028}
1029
1030fn calculate_transaction_size(tx: &Transaction) -> usize {
1034 use crate::transaction::calculate_transaction_size as tx_size;
1035 tx_size(tx)
1036}
1037
1038fn is_coinbase(tx: &Transaction) -> bool {
1040 #[cfg(feature = "production")]
1042 {
1043 use crate::optimizations::constant_folding::is_zero_hash;
1044 tx.inputs.len() == 1
1045 && is_zero_hash(&tx.inputs[0].prevout.hash)
1046 && tx.inputs[0].prevout.index == 0xffffffff
1047 }
1048
1049 #[cfg(not(feature = "production"))]
1050 {
1051 tx.inputs.len() == 1
1052 && tx.inputs[0].prevout.hash == [0u8; 32]
1053 && tx.inputs[0].prevout.index == 0xffffffff
1054 }
1055}
1056
1057#[cfg(test)]
1077mod tests {
1078 use super::*;
1079 use crate::opcodes::*;
1080
1081 #[test]
1082 fn test_accept_to_memory_pool_valid() {
1083 let tx = create_valid_transaction();
1085 let utxo_set = create_test_utxo_set();
1086 let mempool = Mempool::new();
1087
1088 let time_context = Some(TimeContext {
1090 network_time: 1234567890,
1091 median_time_past: 1234567890,
1092 });
1093 let result =
1094 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
1095 assert!(matches!(result, MempoolResult::Accepted));
1096 }
1097
1098 #[test]
1099 fn test_accept_to_memory_pool_duplicate() {
1100 let tx = create_valid_transaction();
1101 let utxo_set = create_test_utxo_set();
1102 let mut mempool = Mempool::new();
1103 mempool.insert(crate::block::calculate_tx_id(&tx));
1104
1105 let time_context = Some(TimeContext {
1106 network_time: 1234567890,
1107 median_time_past: 1234567890,
1108 });
1109 let result =
1110 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
1111 assert!(matches!(result, MempoolResult::Rejected(_)));
1112 }
1113
1114 #[test]
1115 fn test_is_standard_tx_valid() {
1116 let tx = create_valid_transaction();
1117 assert!(is_standard_tx(&tx).unwrap());
1118 }
1119
1120 #[test]
1121 fn test_is_standard_tx_too_large() {
1122 let mut tx = create_valid_transaction();
1123 for _ in 0..MAX_INPUTS {
1126 tx.inputs.push(create_dummy_input());
1127 }
1128 assert!(!is_standard_tx(&tx).unwrap());
1130 }
1131
1132 #[test]
1133 fn test_replacement_checks_all_requirements() {
1134 let utxo_set = create_test_utxo_set();
1135 let mempool = Mempool::new();
1136
1137 let mut existing_tx = create_valid_transaction();
1139 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1140 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1147 new_tx.outputs[0].value = 8000; new_tx.outputs[0].value = 7999; let result = replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1153 assert!(result, "Valid RBF replacement should be accepted");
1154 }
1155
1156 #[test]
1157 fn test_replacement_checks_no_rbf_signal() {
1158 let utxo_set = create_test_utxo_set();
1159 let mempool = Mempool::new();
1160
1161 let new_tx = create_valid_transaction();
1162 let existing_tx = create_valid_transaction(); assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1166 }
1167
1168 #[test]
1169 fn test_replacement_checks_no_conflict() {
1170 let mut utxo_set = create_test_utxo_set();
1171 let new_outpoint = OutPoint {
1173 hash: [2; 32],
1174 index: 0,
1175 };
1176 let new_utxo = UTXO {
1177 value: 10000,
1178 script_pubkey: vec![OP_1].into(),
1179 height: 0,
1180 is_coinbase: false,
1181 };
1182 utxo_set.insert(new_outpoint, std::sync::Arc::new(new_utxo));
1183
1184 let mempool = Mempool::new();
1185
1186 let mut existing_tx = create_valid_transaction();
1187 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1188
1189 let mut new_tx = create_valid_transaction();
1191 new_tx.inputs[0].prevout.hash = [2; 32]; new_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1193 new_tx.outputs[0].value = 5000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1198 }
1199
1200 #[test]
1201 fn test_replacement_checks_fee_rate_too_low() {
1202 let utxo_set = create_test_utxo_set();
1203 let mempool = Mempool::new();
1204
1205 let mut existing_tx = create_valid_transaction();
1207 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1208 existing_tx.outputs[0].value = 5000; let mut new_tx = existing_tx.clone();
1212 new_tx.outputs[0].value = 4999; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1216 }
1217
1218 #[test]
1219 fn test_replacement_checks_absolute_fee_insufficient() {
1220 let utxo_set = create_test_utxo_set();
1221 let mempool = Mempool::new();
1222
1223 let mut existing_tx = create_valid_transaction();
1225 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1226 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1231 new_tx.outputs[0].value = 8001; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1235
1236 new_tx.outputs[0].value = 7999; }
1241
1242 #[test]
1247 fn test_accept_to_memory_pool_coinbase() {
1248 let coinbase_tx = create_coinbase_transaction();
1249 let utxo_set = UtxoSet::default();
1250 let mempool = Mempool::new();
1251 let time_context = Some(TimeContext {
1253 network_time: 0,
1254 median_time_past: 0,
1255 });
1256 let result =
1257 accept_to_memory_pool(&coinbase_tx, None, &utxo_set, &mempool, 100, time_context)
1258 .unwrap();
1259 assert!(matches!(result, MempoolResult::Rejected(_)));
1260 }
1261
1262 #[test]
1263 fn test_is_standard_tx_large_script() {
1264 let mut tx = create_valid_transaction();
1265 tx.inputs[0].script_sig = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1267
1268 let result = is_standard_tx(&tx).unwrap();
1269 assert!(!result);
1270 }
1271
1272 #[test]
1273 fn test_is_standard_tx_large_output_script() {
1274 let mut tx = create_valid_transaction();
1275 tx.outputs[0].script_pubkey = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1277
1278 let result = is_standard_tx(&tx).unwrap();
1279 assert!(!result);
1280 }
1281
1282 #[test]
1283 fn test_replacement_checks_new_unconfirmed_dependency() {
1284 let utxo_set = create_test_utxo_set();
1285 let mempool = Mempool::new();
1286
1287 let mut existing_tx = create_valid_transaction();
1289 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1290
1291 let mut new_tx = existing_tx.clone();
1293 new_tx.inputs.push(TransactionInput {
1294 prevout: OutPoint {
1295 hash: [99; 32],
1296 index: 0,
1297 }, script_sig: vec![],
1299 sequence: SEQUENCE_RBF as u64,
1300 });
1301 new_tx.outputs[0].value = 7000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1305 }
1306
1307 #[test]
1308 fn test_has_conflict_with_tx_true() {
1309 let tx1 = create_valid_transaction();
1310 let mut tx2 = create_valid_transaction();
1311 tx2.inputs[0].prevout = tx1.inputs[0].prevout; assert!(has_conflict_with_tx(&tx2, &tx1));
1314 }
1315
1316 #[test]
1317 fn test_has_conflict_with_tx_false() {
1318 let tx1 = create_valid_transaction();
1319 let mut tx2 = create_valid_transaction();
1320 tx2.inputs[0].prevout.hash = [2; 32]; assert!(!has_conflict_with_tx(&tx2, &tx1));
1323 }
1324
1325 #[test]
1326 fn test_replacement_checks_minimum_relay_fee() {
1327 let utxo_set = create_test_utxo_set();
1328 let mempool = Mempool::new();
1329
1330 let mut existing_tx = create_valid_transaction();
1332 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1333 existing_tx.outputs[0].value = 9500; let mut new_tx = existing_tx.clone();
1337 new_tx.outputs[0].value = 8500; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1339
1340 new_tx.outputs[0].value = 8499;
1344 }
1345
1346 #[test]
1347 fn test_check_mempool_rules_low_fee() {
1348 let tx = create_valid_transaction();
1349 let fee = 1; let mempool = Mempool::new();
1351
1352 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1353 assert!(!result);
1354 }
1355
1356 #[test]
1357 fn test_check_mempool_rules_high_fee() {
1358 let tx = create_valid_transaction();
1359 let fee = 10000; let mempool = Mempool::new();
1361
1362 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1363 assert!(result);
1364 }
1365
1366 #[test]
1367 fn test_check_mempool_rules_full_mempool() {
1368 let tx = create_valid_transaction();
1369 let fee = 10000;
1370 let mut mempool = Mempool::new();
1371
1372 for i in 0..100_001 {
1375 let mut hash = [0u8; 32];
1376 hash[0] = (i & 0xff) as u8;
1377 hash[1] = ((i >> 8) & 0xff) as u8;
1378 hash[2] = ((i >> 16) & 0xff) as u8;
1379 hash[3] = ((i >> 24) & 0xff) as u8;
1380 mempool.insert(hash);
1381 }
1382
1383 assert!(mempool.len() > 100_000);
1385
1386 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1387 assert!(!result);
1388 }
1389
1390 #[test]
1391 fn test_has_conflicts_no_conflicts() {
1392 let tx = create_valid_transaction();
1393 let mempool = Mempool::new();
1394
1395 let result = has_conflicts(&tx, &mempool).unwrap();
1396 assert!(!result);
1397 }
1398
1399 #[test]
1400 fn test_has_conflicts_with_conflicts() {
1401 let tx = create_valid_transaction();
1402 let mut mempool = Mempool::new();
1403
1404 mempool.insert(tx.inputs[0].prevout.hash);
1406
1407 let result = has_conflicts(&tx, &mempool).unwrap();
1408 assert!(result);
1409 }
1410
1411 #[test]
1412 fn test_signals_rbf_true() {
1413 let mut tx = create_valid_transaction();
1414 tx.inputs[0].sequence = 0xfffffffe; assert!(signals_rbf(&tx));
1417 }
1418
1419 #[test]
1420 fn test_signals_rbf_false() {
1421 let tx = create_valid_transaction(); assert!(!signals_rbf(&tx));
1424 }
1425
1426 #[test]
1427 fn test_calculate_fee_rate() {
1428 let tx = create_valid_transaction();
1429 let utxo_set = create_test_utxo_set();
1430 let fee = calculate_fee(&tx, &utxo_set);
1431
1432 assert!(fee.is_ok());
1434 }
1435
1436 #[test]
1437 fn test_creates_new_dependencies_no_new() {
1438 let new_tx = create_valid_transaction();
1439 let existing_tx = create_valid_transaction();
1440 let mempool = Mempool::new();
1441
1442 let utxo_set = create_test_utxo_set();
1443 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1444 assert!(!result);
1445 }
1446
1447 #[test]
1448 fn test_creates_new_dependencies_with_new() {
1449 let mut new_tx = create_valid_transaction();
1450 let existing_tx = create_valid_transaction();
1451 let mempool = Mempool::new();
1452
1453 new_tx.inputs[0].prevout.hash = [2; 32];
1455
1456 let utxo_set = create_test_utxo_set();
1457 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1458 assert!(result);
1459 }
1460
1461 #[test]
1462 fn test_is_standard_script_empty() {
1463 let script = vec![];
1464 let result = is_standard_script(&script).unwrap();
1465 assert!(!result);
1466 }
1467
1468 #[test]
1469 fn test_is_standard_script_too_large() {
1470 let script = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1471 let result = is_standard_script(&script).unwrap();
1472 assert!(!result);
1473 }
1474
1475 #[test]
1476 fn test_is_standard_script_non_standard_opcode() {
1477 let script = vec![OP_VERIF]; let result = is_standard_script(&script).unwrap();
1479 assert!(!result);
1480 }
1481
1482 #[test]
1483 fn test_is_standard_script_valid() {
1484 let script = vec![OP_1];
1485 let result = is_standard_script(&script).unwrap();
1486 assert!(result);
1487 }
1488
1489 #[test]
1490 fn test_calculate_tx_id() {
1491 let tx = create_valid_transaction();
1492 let tx_id = crate::block::calculate_tx_id(&tx);
1493
1494 assert_eq!(tx_id.len(), 32);
1496
1497 let tx_id2 = crate::block::calculate_tx_id(&tx);
1499 assert_eq!(tx_id, tx_id2);
1500 }
1501
1502 #[test]
1503 fn test_calculate_tx_id_different_txs() {
1504 let tx1 = create_valid_transaction();
1505 let mut tx2 = tx1.clone();
1506 tx2.version = 2; let id1 = crate::block::calculate_tx_id(&tx1);
1509 let id2 = crate::block::calculate_tx_id(&tx2);
1510
1511 assert_ne!(id1, id2);
1512 }
1513
1514 #[test]
1515 fn test_calculate_transaction_size() {
1516 let tx = create_valid_transaction();
1517 let size = calculate_transaction_size(&tx);
1518
1519 assert!(size > 0);
1520
1521 let size2 = calculate_transaction_size(&tx);
1523 assert_eq!(size, size2);
1524 }
1525
1526 #[test]
1527 fn test_calculate_transaction_size_multiple_inputs_outputs() {
1528 let mut tx = create_valid_transaction();
1529 tx.inputs.push(create_dummy_input());
1530 tx.outputs.push(create_dummy_output());
1531
1532 let size = calculate_transaction_size(&tx);
1533 assert!(size > 0);
1534 }
1535
1536 #[test]
1537 fn test_is_coinbase_true() {
1538 let coinbase_tx = create_coinbase_transaction();
1539 assert!(is_coinbase(&coinbase_tx));
1540 }
1541
1542 #[test]
1543 fn test_is_coinbase_false() {
1544 let regular_tx = create_valid_transaction();
1545 assert!(!is_coinbase(®ular_tx));
1546 }
1547
1548 fn create_valid_transaction() -> Transaction {
1550 Transaction {
1551 version: 1,
1552 inputs: vec![create_dummy_input()].into(),
1553 outputs: vec![create_dummy_output()].into(),
1554 lock_time: 0,
1555 }
1556 }
1557
1558 fn create_dummy_input() -> TransactionInput {
1559 TransactionInput {
1560 prevout: OutPoint {
1561 hash: [1; 32],
1562 index: 0,
1563 },
1564 script_sig: vec![OP_1],
1565 sequence: 0xffffffff,
1566 }
1567 }
1568
1569 fn create_dummy_output() -> TransactionOutput {
1570 TransactionOutput {
1571 value: 1000,
1572 script_pubkey: vec![OP_1], }
1574 }
1575
1576 fn create_test_utxo_set() -> UtxoSet {
1577 let mut utxo_set = UtxoSet::default();
1578 let outpoint = OutPoint {
1579 hash: [1; 32],
1580 index: 0,
1581 };
1582 let utxo = UTXO {
1583 value: 10000,
1584 script_pubkey: vec![OP_1].into(), height: 0,
1586 is_coinbase: false,
1587 };
1588 utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
1589 utxo_set
1590 }
1591
1592 fn create_coinbase_transaction() -> Transaction {
1593 Transaction {
1594 version: 1,
1595 inputs: vec![TransactionInput {
1596 prevout: OutPoint {
1597 hash: [0; 32],
1598 index: 0xffffffff,
1599 },
1600 script_sig: vec![],
1601 sequence: 0xffffffff,
1602 }]
1603 .into(),
1604 outputs: vec![TransactionOutput {
1605 value: 5000000000,
1606 script_pubkey: vec![],
1607 }]
1608 .into(),
1609 lock_time: 0,
1610 }
1611 }
1612}