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
240pub fn is_standard_tx_with_config(
246 tx: &crate::types::Transaction,
247 config: Option<&blvm_primitives::config::MempoolConfig>,
248) -> Result<bool> {
249 let Some(cfg) = config else {
250 return is_standard_tx(tx);
251 };
252
253 let tx_size = crate::transaction::calculate_transaction_size(tx);
255 if tx_size > MAX_TX_SIZE {
256 return Ok(false);
257 }
258 for input in tx.inputs.iter() {
259 if input.script_sig.len() > MAX_SCRIPT_SIZE {
260 return Ok(false);
261 }
262 }
263
264 let max_script = cfg.max_standard_script_size as usize;
265 let max_op_return = cfg.max_op_return_size as usize;
266 let reject_envelope = cfg.reject_envelope_protocol;
267 let reject_multi_op_return = cfg.reject_multiple_op_return;
268
269 let mut op_return_count = 0usize;
270 for output in tx.outputs.iter() {
271 let s = &output.script_pubkey;
272
273 if s.len() > max_script {
275 return Ok(false);
276 }
277
278 if s.first() == Some(&0x6a) {
279 op_return_count += 1;
281 if s.len().saturating_sub(1) > max_op_return {
282 return Ok(false);
283 }
284 } else if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
285 if reject_envelope {
287 return Ok(false);
288 }
289 }
290 }
291
292 if reject_multi_op_return && op_return_count > 1 {
294 return Ok(false);
295 }
296
297 Ok(true)
298}
299
300#[spec_locked("9.2", "IsStandardTx")]
308pub fn is_standard_tx(tx: &Transaction) -> Result<bool> {
309 let tx_size = calculate_transaction_size(tx);
311 if tx_size > MAX_TX_SIZE {
312 return Ok(false);
313 }
314
315 for (i, input) in tx.inputs.iter().enumerate() {
317 assert!(i < tx.inputs.len(), "Input index {i} out of bounds");
319 assert!(
321 input.script_sig.len() <= MAX_SCRIPT_SIZE * 2,
322 "Script size {} must be reasonable for input {}",
323 input.script_sig.len(),
324 i
325 );
326 if input.script_sig.len() > MAX_SCRIPT_SIZE {
327 return Ok(false);
328 }
329 }
330
331 for (i, output) in tx.outputs.iter().enumerate() {
332 assert!(i < tx.outputs.len(), "Output index {i} out of bounds");
334 assert!(
336 output.script_pubkey.len() <= MAX_SCRIPT_SIZE * 2,
337 "Script size {} must be reasonable for output {}",
338 output.script_pubkey.len(),
339 i
340 );
341 if output.script_pubkey.len() > MAX_SCRIPT_SIZE {
342 return Ok(false);
343 }
344 }
345
346 let mut op_return_count = 0usize;
348 for (i, output) in tx.outputs.iter().enumerate() {
349 assert!(
351 i < tx.outputs.len(),
352 "Output index {i} out of bounds in standard check"
353 );
354 if !is_standard_script(&output.script_pubkey)? {
355 return Ok(false);
356 }
357
358 if output.script_pubkey.first() == Some(&0x6a) {
360 op_return_count += 1;
361 }
362
363 let s = &output.script_pubkey;
365 if s.len() >= 2 && s[0] == 0x00 && s[1] == 0x63 {
366 return Ok(false);
367 }
368 }
369
370 if op_return_count > 1 {
372 return Ok(false);
373 }
374
375 Ok(true)
376}
377
378#[spec_locked("9.3", "ReplacementChecks")]
389pub fn replacement_checks(
390 new_tx: &Transaction,
391 existing_tx: &Transaction,
392 utxo_set: &UtxoSet,
393 mempool: &Mempool,
394) -> Result<bool> {
395 if new_tx.inputs.is_empty() && new_tx.outputs.is_empty() {
400 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
401 "New transaction must have at least one input or output"
402 .to_string()
403 .into(),
404 ));
405 }
406 if existing_tx.inputs.is_empty() && existing_tx.outputs.is_empty() {
407 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
408 "Existing transaction must have at least one input or output"
409 .to_string()
410 .into(),
411 ));
412 }
413 if is_coinbase(new_tx) {
414 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
415 "New transaction cannot be coinbase".to_string().into(),
416 ));
417 }
418 if is_coinbase(existing_tx) {
419 return Err(crate::error::ConsensusError::ConsensusRuleViolation(
420 "Existing transaction cannot be coinbase".to_string().into(),
421 ));
422 }
423 assert!(
424 utxo_set.len() <= u32::MAX as usize,
425 "UTXO set size {} exceeds maximum",
426 utxo_set.len()
427 );
428
429 if !signals_rbf(existing_tx) {
432 return Ok(false);
433 }
434
435 let new_fee = calculate_fee(new_tx, utxo_set)?;
437 let existing_fee = calculate_fee(existing_tx, utxo_set)?;
438 assert!(new_fee >= 0, "New fee {new_fee} must be non-negative");
440 assert!(
441 existing_fee >= 0,
442 "Existing fee {existing_fee} must be non-negative"
443 );
444 use crate::constants::MAX_MONEY;
445 assert!(
446 new_fee <= MAX_MONEY,
447 "New fee {new_fee} must not exceed MAX_MONEY"
448 );
449 assert!(
450 existing_fee <= MAX_MONEY,
451 "Existing fee {existing_fee} must not exceed MAX_MONEY"
452 );
453
454 let new_tx_size = calculate_transaction_size_vbytes(new_tx);
455 let existing_tx_size = calculate_transaction_size_vbytes(existing_tx);
456 assert!(
458 new_tx_size > 0,
459 "New transaction size {new_tx_size} must be positive"
460 );
461 assert!(
462 existing_tx_size > 0,
463 "Existing transaction size {existing_tx_size} must be positive"
464 );
465 assert!(
466 new_tx_size <= MAX_TX_SIZE * 2,
467 "New transaction size {new_tx_size} must be reasonable"
468 );
469 assert!(
470 existing_tx_size <= MAX_TX_SIZE * 2,
471 "Existing transaction size {existing_tx_size} must be reasonable"
472 );
473
474 if new_tx_size == 0 || existing_tx_size == 0 {
475 return Ok(false);
476 }
477
478 debug_assert!(
485 new_tx_size > 0,
486 "New transaction size ({new_tx_size}) must be positive"
487 );
488 debug_assert!(
489 existing_tx_size > 0,
490 "Existing transaction size ({existing_tx_size}) must be positive"
491 );
492
493 let new_fee_scaled = (new_fee as u128)
496 .checked_mul(existing_tx_size as u128)
497 .ok_or_else(|| {
498 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
499 })?;
500 let existing_fee_scaled = (existing_fee as u128)
501 .checked_mul(new_tx_size as u128)
502 .ok_or_else(|| {
503 ConsensusError::TransactionValidation("Fee rate calculation overflow".into())
504 })?;
505
506 if new_fee_scaled <= existing_fee_scaled {
507 return Ok(false);
508 }
509
510 if new_fee <= existing_fee + MIN_RELAY_FEE {
512 return Ok(false);
513 }
514
515 if !has_conflict_with_tx(new_tx, existing_tx) {
517 return Ok(false);
518 }
519
520 if creates_new_dependencies(new_tx, existing_tx, utxo_set, mempool)? {
523 return Ok(false);
524 }
525
526 Ok(true)
527}
528
529pub type Mempool = HashSet<Hash>;
535
536#[derive(Debug, Clone, PartialEq, Eq)]
538pub enum MempoolResult {
539 Accepted,
540 Rejected(String),
541}
542
543pub fn update_mempool_after_block(
604 mempool: &mut Mempool,
605 block: &crate::types::Block,
606 _utxo_set: &crate::types::UtxoSet,
607) -> Result<Vec<Hash>> {
608 let mut removed = Vec::new();
609
610 for tx in &block.transactions {
612 let tx_id = crate::block::calculate_tx_id(tx);
613 if mempool.remove(&tx_id) {
614 removed.push(tx_id);
615 }
616 }
617
618 Ok(removed)
626}
627
628pub fn update_mempool_after_block_with_lookup<F>(
643 mempool: &mut Mempool,
644 block: &crate::types::Block,
645 get_tx_by_id: F,
646) -> Result<Vec<Hash>>
647where
648 F: Fn(&Hash) -> Option<crate::types::Transaction>,
649{
650 let mut removed = Vec::new();
651
652 for tx in &block.transactions {
654 let tx_id = crate::block::calculate_tx_id(tx);
655 if mempool.remove(&tx_id) {
656 removed.push(tx_id);
657 }
658 }
659
660 let mut spent_outpoints = std::collections::HashSet::new();
663 for tx in &block.transactions {
664 if !crate::transaction::is_coinbase(tx) {
665 for input in &tx.inputs {
666 spent_outpoints.insert(input.prevout);
667 }
668 }
669 }
670
671 let mut invalid_tx_ids = Vec::new();
673 for &tx_id in mempool.iter() {
674 if let Some(tx) = get_tx_by_id(&tx_id) {
675 for input in &tx.inputs {
677 if spent_outpoints.contains(&input.prevout) {
678 invalid_tx_ids.push(tx_id);
679 break;
680 }
681 }
682 }
683 }
684
685 for tx_id in invalid_tx_ids {
687 if mempool.remove(&tx_id) {
688 removed.push(tx_id);
689 }
690 }
691
692 Ok(removed)
693}
694
695fn check_mempool_rules(tx: &Transaction, fee: Integer, mempool: &Mempool) -> Result<bool> {
697 let tx_size = calculate_transaction_size(tx);
699 debug_assert!(
703 tx_size > 0,
704 "Transaction size ({tx_size}) must be positive for fee rate calculation"
705 );
706
707 let fee_rate = (fee as f64) / (tx_size as f64);
708
709 debug_assert!(
711 fee_rate >= 0.0,
712 "Fee rate ({fee_rate:.6}) must be non-negative (fee: {fee}, size: {tx_size})"
713 );
714
715 let config = crate::config::get_consensus_config_ref();
717 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 {
722 return Ok(false);
723 }
724
725 if fee_rate < min_fee_rate {
727 return Ok(false);
728 }
729
730 if mempool.len() > config.mempool.max_mempool_txs {
733 return Ok(false);
734 }
735
736 Ok(true)
737}
738
739fn has_conflicts(tx: &Transaction, mempool: &Mempool) -> Result<bool> {
741 for input in &tx.inputs {
743 if mempool.contains(&input.prevout.hash) {
747 return Ok(true);
748 }
749 }
750
751 Ok(false)
752}
753
754#[spec_locked("9.1.1", "CheckFinalTxAtTip")]
795pub fn is_final_tx(tx: &Transaction, height: Natural, block_time: Natural) -> bool {
796 use crate::constants::SEQUENCE_FINAL;
797
798 if tx.lock_time == 0 {
800 return true;
801 }
802
803 let locktime_satisfied = if (tx.lock_time as u32) < LOCKTIME_THRESHOLD {
809 (tx.lock_time as Natural) < height
811 } else {
812 (tx.lock_time as Natural) < block_time
814 };
815
816 if locktime_satisfied {
817 return true;
818 }
819
820 for input in &tx.inputs {
824 if (input.sequence as u32) != SEQUENCE_FINAL {
825 return false;
826 }
827 }
828
829 true
831}
832
833pub fn signals_rbf(tx: &Transaction) -> bool {
837 for input in &tx.inputs {
838 if (input.sequence as u32) < SEQUENCE_FINAL {
839 return true;
840 }
841 }
842 false
843}
844
845fn calculate_transaction_size_vbytes(tx: &Transaction) -> usize {
851 calculate_transaction_size(tx)
854}
855
856pub fn has_conflict_with_tx(new_tx: &Transaction, existing_tx: &Transaction) -> bool {
861 for new_input in &new_tx.inputs {
862 for existing_input in &existing_tx.inputs {
863 if new_input.prevout == existing_input.prevout {
864 return true;
865 }
866 }
867 }
868 false
869}
870
871fn creates_new_dependencies(
877 new_tx: &Transaction,
878 existing_tx: &Transaction,
879 utxo_set: &UtxoSet,
880 mempool: &Mempool,
881) -> Result<bool> {
882 for input in &new_tx.inputs {
883 if utxo_set.contains_key(&input.prevout) {
885 continue;
886 }
887
888 let mut found_in_existing = false;
890 for existing_input in &existing_tx.inputs {
891 if existing_input.prevout == input.prevout {
892 found_in_existing = true;
893 break;
894 }
895 }
896
897 if found_in_existing {
898 continue;
899 }
900
901 if !mempool.contains(&input.prevout.hash) {
904 return Ok(true); }
906 }
907
908 Ok(false)
909}
910
911fn script_opcode_advance(script: &[u8], pc: usize) -> usize {
913 let opcode = script[pc];
914 match opcode {
915 0x01..=0x4b => 1 + opcode as usize,
916 0x4c => {
917 if pc + 1 < script.len() {
918 2 + script[pc + 1] as usize
919 } else {
920 1
921 }
922 }
923 0x4d => {
924 if pc + 2 < script.len() {
925 3 + u16::from_le_bytes([script[pc + 1], script[pc + 2]]) as usize
926 } else {
927 1
928 }
929 }
930 0x4e => {
931 if pc + 4 < script.len() {
932 5 + u32::from_le_bytes([
933 script[pc + 1],
934 script[pc + 2],
935 script[pc + 3],
936 script[pc + 4],
937 ]) as usize
938 } else {
939 1
940 }
941 }
942 _ => 1,
943 }
944}
945
946#[inline]
948fn is_disabled_policy_opcode(opcode: u8) -> bool {
949 use crate::opcodes::{
950 OP_2DIV, OP_2MUL, OP_AND, OP_CAT, OP_DIV, OP_INVERT, OP_LEFT, OP_LSHIFT, OP_MOD, OP_MUL,
951 OP_OR, OP_RIGHT, OP_RSHIFT, OP_SUBSTR, OP_VER, OP_VERIF, OP_VERNOTIF, OP_XOR,
952 };
953 matches!(
954 opcode,
955 OP_VER
956 | OP_VERIF
957 | OP_VERNOTIF
958 | OP_CAT
959 | OP_SUBSTR
960 | OP_LEFT
961 | OP_RIGHT
962 | OP_INVERT
963 | OP_AND
964 | OP_OR
965 | OP_XOR
966 | OP_2MUL
967 | OP_2DIV
968 | OP_MUL
969 | OP_DIV
970 | OP_MOD
971 | OP_LSHIFT
972 | OP_RSHIFT
973 )
974}
975
976fn is_standard_script(script: &ByteString) -> Result<bool> {
978 if script.is_empty() {
979 return Ok(false);
980 }
981
982 if script.len() > MAX_SCRIPT_SIZE {
983 return Ok(false);
984 }
985
986 if script[0] == 0x6a {
989 return Ok(script.len() <= 83);
991 }
992
993 let mut pc = 0;
995 while pc < script.len() {
996 let opcode = script[pc];
997 if is_disabled_policy_opcode(opcode) {
998 return Ok(false);
999 }
1000 let advance = script_opcode_advance(script, pc);
1001 if advance == 0 {
1002 break;
1003 }
1004 pc += advance;
1005 }
1006
1007 Ok(true)
1008}
1009
1010#[deprecated(note = "Use crate::block::calculate_tx_id instead")]
1015#[spec_locked("5.1", "CalculateTxId")]
1016pub fn calculate_tx_id(tx: &Transaction) -> Hash {
1017 crate::block::calculate_tx_id(tx)
1018}
1019
1020fn calculate_transaction_size(tx: &Transaction) -> usize {
1024 use crate::transaction::calculate_transaction_size as tx_size;
1025 tx_size(tx)
1026}
1027
1028fn is_coinbase(tx: &Transaction) -> bool {
1030 #[cfg(feature = "production")]
1032 {
1033 use crate::optimizations::constant_folding::is_zero_hash;
1034 tx.inputs.len() == 1
1035 && is_zero_hash(&tx.inputs[0].prevout.hash)
1036 && tx.inputs[0].prevout.index == 0xffffffff
1037 }
1038
1039 #[cfg(not(feature = "production"))]
1040 {
1041 tx.inputs.len() == 1
1042 && tx.inputs[0].prevout.hash == [0u8; 32]
1043 && tx.inputs[0].prevout.index == 0xffffffff
1044 }
1045}
1046
1047#[cfg(test)]
1067mod tests {
1068 use super::*;
1069 use crate::opcodes::*;
1070
1071 #[test]
1072 fn test_accept_to_memory_pool_valid() {
1073 let tx = create_valid_transaction();
1075 let utxo_set = create_test_utxo_set();
1076 let mempool = Mempool::new();
1077
1078 let time_context = Some(TimeContext {
1080 network_time: 1234567890,
1081 median_time_past: 1234567890,
1082 });
1083 let result =
1084 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
1085 assert!(matches!(result, MempoolResult::Rejected(_)));
1086 }
1087
1088 #[test]
1089 fn test_accept_to_memory_pool_duplicate() {
1090 let tx = create_valid_transaction();
1091 let utxo_set = create_test_utxo_set();
1092 let mut mempool = Mempool::new();
1093 mempool.insert(crate::block::calculate_tx_id(&tx));
1094
1095 let time_context = Some(TimeContext {
1096 network_time: 1234567890,
1097 median_time_past: 1234567890,
1098 });
1099 let result =
1100 accept_to_memory_pool(&tx, None, &utxo_set, &mempool, 100, time_context).unwrap();
1101 assert!(matches!(result, MempoolResult::Rejected(_)));
1102 }
1103
1104 #[test]
1105 fn test_is_standard_tx_valid() {
1106 let tx = create_valid_transaction();
1107 assert!(is_standard_tx(&tx).unwrap());
1108 }
1109
1110 #[test]
1111 fn test_is_standard_tx_too_large() {
1112 let mut tx = create_valid_transaction();
1113 for _ in 0..MAX_INPUTS {
1116 tx.inputs.push(create_dummy_input());
1117 }
1118 assert!(!is_standard_tx(&tx).unwrap());
1120 }
1121
1122 #[test]
1123 fn test_replacement_checks_all_requirements() {
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 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1137 new_tx.outputs[0].value = 8000; new_tx.outputs[0].value = 7999; let result = replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1143 assert!(result, "Valid RBF replacement should be accepted");
1144 }
1145
1146 #[test]
1147 fn test_replacement_checks_no_rbf_signal() {
1148 let utxo_set = create_test_utxo_set();
1149 let mempool = Mempool::new();
1150
1151 let new_tx = create_valid_transaction();
1152 let existing_tx = create_valid_transaction(); assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1156 }
1157
1158 #[test]
1159 fn test_replacement_checks_no_conflict() {
1160 let mut utxo_set = create_test_utxo_set();
1161 let new_outpoint = OutPoint {
1163 hash: [2; 32],
1164 index: 0,
1165 };
1166 let new_utxo = UTXO {
1167 value: 10000,
1168 script_pubkey: vec![OP_1].into(),
1169 height: 0,
1170 is_coinbase: false,
1171 };
1172 utxo_set.insert(new_outpoint, std::sync::Arc::new(new_utxo));
1173
1174 let mempool = Mempool::new();
1175
1176 let mut existing_tx = create_valid_transaction();
1177 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1178
1179 let mut new_tx = create_valid_transaction();
1181 new_tx.inputs[0].prevout.hash = [2; 32]; new_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1183 new_tx.outputs[0].value = 5000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1188 }
1189
1190 #[test]
1191 fn test_replacement_checks_fee_rate_too_low() {
1192 let utxo_set = create_test_utxo_set();
1193 let mempool = Mempool::new();
1194
1195 let mut existing_tx = create_valid_transaction();
1197 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1198 existing_tx.outputs[0].value = 5000; let mut new_tx = existing_tx.clone();
1202 new_tx.outputs[0].value = 4999; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1206 }
1207
1208 #[test]
1209 fn test_replacement_checks_absolute_fee_insufficient() {
1210 let utxo_set = create_test_utxo_set();
1211 let mempool = Mempool::new();
1212
1213 let mut existing_tx = create_valid_transaction();
1215 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1216 existing_tx.outputs[0].value = 9000; let mut new_tx = existing_tx.clone();
1221 new_tx.outputs[0].value = 8001; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1225
1226 new_tx.outputs[0].value = 7999; }
1231
1232 #[test]
1237 fn test_accept_to_memory_pool_coinbase() {
1238 let coinbase_tx = create_coinbase_transaction();
1239 let utxo_set = UtxoSet::default();
1240 let mempool = Mempool::new();
1241 let time_context = Some(TimeContext {
1243 network_time: 0,
1244 median_time_past: 0,
1245 });
1246 let result =
1247 accept_to_memory_pool(&coinbase_tx, None, &utxo_set, &mempool, 100, time_context)
1248 .unwrap();
1249 assert!(matches!(result, MempoolResult::Rejected(_)));
1250 }
1251
1252 #[test]
1253 fn test_is_standard_tx_large_script() {
1254 let mut tx = create_valid_transaction();
1255 tx.inputs[0].script_sig = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1257
1258 let result = is_standard_tx(&tx).unwrap();
1259 assert!(!result);
1260 }
1261
1262 #[test]
1263 fn test_is_standard_tx_large_output_script() {
1264 let mut tx = create_valid_transaction();
1265 tx.outputs[0].script_pubkey = 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_replacement_checks_new_unconfirmed_dependency() {
1274 let utxo_set = create_test_utxo_set();
1275 let mempool = Mempool::new();
1276
1277 let mut existing_tx = create_valid_transaction();
1279 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1280
1281 let mut new_tx = existing_tx.clone();
1283 new_tx.inputs.push(TransactionInput {
1284 prevout: OutPoint {
1285 hash: [99; 32],
1286 index: 0,
1287 }, script_sig: vec![],
1289 sequence: SEQUENCE_RBF as u64,
1290 });
1291 new_tx.outputs[0].value = 7000; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1295 }
1296
1297 #[test]
1298 fn test_has_conflict_with_tx_true() {
1299 let tx1 = create_valid_transaction();
1300 let mut tx2 = create_valid_transaction();
1301 tx2.inputs[0].prevout = tx1.inputs[0].prevout.clone(); assert!(has_conflict_with_tx(&tx2, &tx1));
1304 }
1305
1306 #[test]
1307 fn test_has_conflict_with_tx_false() {
1308 let tx1 = create_valid_transaction();
1309 let mut tx2 = create_valid_transaction();
1310 tx2.inputs[0].prevout.hash = [2; 32]; assert!(!has_conflict_with_tx(&tx2, &tx1));
1313 }
1314
1315 #[test]
1316 fn test_replacement_checks_minimum_relay_fee() {
1317 let utxo_set = create_test_utxo_set();
1318 let mempool = Mempool::new();
1319
1320 let mut existing_tx = create_valid_transaction();
1322 existing_tx.inputs[0].sequence = SEQUENCE_RBF as u64;
1323 existing_tx.outputs[0].value = 9500; let mut new_tx = existing_tx.clone();
1327 new_tx.outputs[0].value = 8500; assert!(!replacement_checks(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap());
1329
1330 new_tx.outputs[0].value = 8499;
1334 }
1335
1336 #[test]
1337 fn test_check_mempool_rules_low_fee() {
1338 let tx = create_valid_transaction();
1339 let fee = 1; let mempool = Mempool::new();
1341
1342 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1343 assert!(!result);
1344 }
1345
1346 #[test]
1347 fn test_check_mempool_rules_high_fee() {
1348 let tx = create_valid_transaction();
1349 let fee = 10000; 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_full_mempool() {
1358 let tx = create_valid_transaction();
1359 let fee = 10000;
1360 let mut mempool = Mempool::new();
1361
1362 for i in 0..100_001 {
1365 let mut hash = [0u8; 32];
1366 hash[0] = (i & 0xff) as u8;
1367 hash[1] = ((i >> 8) & 0xff) as u8;
1368 hash[2] = ((i >> 16) & 0xff) as u8;
1369 hash[3] = ((i >> 24) & 0xff) as u8;
1370 mempool.insert(hash);
1371 }
1372
1373 assert!(mempool.len() > 100_000);
1375
1376 let result = check_mempool_rules(&tx, fee, &mempool).unwrap();
1377 assert!(!result);
1378 }
1379
1380 #[test]
1381 fn test_has_conflicts_no_conflicts() {
1382 let tx = create_valid_transaction();
1383 let mempool = Mempool::new();
1384
1385 let result = has_conflicts(&tx, &mempool).unwrap();
1386 assert!(!result);
1387 }
1388
1389 #[test]
1390 fn test_has_conflicts_with_conflicts() {
1391 let tx = create_valid_transaction();
1392 let mut mempool = Mempool::new();
1393
1394 mempool.insert(tx.inputs[0].prevout.hash);
1396
1397 let result = has_conflicts(&tx, &mempool).unwrap();
1398 assert!(result);
1399 }
1400
1401 #[test]
1402 fn test_signals_rbf_true() {
1403 let mut tx = create_valid_transaction();
1404 tx.inputs[0].sequence = 0xfffffffe; assert!(signals_rbf(&tx));
1407 }
1408
1409 #[test]
1410 fn test_signals_rbf_false() {
1411 let tx = create_valid_transaction(); assert!(!signals_rbf(&tx));
1414 }
1415
1416 #[test]
1417 fn test_calculate_fee_rate() {
1418 let tx = create_valid_transaction();
1419 let utxo_set = create_test_utxo_set();
1420 let fee = calculate_fee(&tx, &utxo_set);
1421
1422 assert!(fee.is_ok());
1424 }
1425
1426 #[test]
1427 fn test_creates_new_dependencies_no_new() {
1428 let new_tx = create_valid_transaction();
1429 let existing_tx = create_valid_transaction();
1430 let mempool = Mempool::new();
1431
1432 let utxo_set = create_test_utxo_set();
1433 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1434 assert!(!result);
1435 }
1436
1437 #[test]
1438 fn test_creates_new_dependencies_with_new() {
1439 let mut new_tx = create_valid_transaction();
1440 let existing_tx = create_valid_transaction();
1441 let mempool = Mempool::new();
1442
1443 new_tx.inputs[0].prevout.hash = [2; 32];
1445
1446 let utxo_set = create_test_utxo_set();
1447 let result = creates_new_dependencies(&new_tx, &existing_tx, &utxo_set, &mempool).unwrap();
1448 assert!(result);
1449 }
1450
1451 #[test]
1452 fn test_is_standard_script_empty() {
1453 let script = vec![];
1454 let result = is_standard_script(&script).unwrap();
1455 assert!(!result);
1456 }
1457
1458 #[test]
1459 fn test_is_standard_script_too_large() {
1460 let script = vec![OP_1; MAX_SCRIPT_SIZE + 1];
1461 let result = is_standard_script(&script).unwrap();
1462 assert!(!result);
1463 }
1464
1465 #[test]
1466 fn test_is_standard_script_non_standard_opcode() {
1467 let script = vec![OP_VERIF]; let result = is_standard_script(&script).unwrap();
1469 assert!(!result);
1470 }
1471
1472 #[test]
1473 fn test_is_standard_script_valid() {
1474 let script = vec![OP_1];
1475 let result = is_standard_script(&script).unwrap();
1476 assert!(result);
1477 }
1478
1479 #[test]
1480 fn test_calculate_tx_id() {
1481 let tx = create_valid_transaction();
1482 let tx_id = crate::block::calculate_tx_id(&tx);
1483
1484 assert_eq!(tx_id.len(), 32);
1486
1487 let tx_id2 = crate::block::calculate_tx_id(&tx);
1489 assert_eq!(tx_id, tx_id2);
1490 }
1491
1492 #[test]
1493 fn test_calculate_tx_id_different_txs() {
1494 let tx1 = create_valid_transaction();
1495 let mut tx2 = tx1.clone();
1496 tx2.version = 2; let id1 = crate::block::calculate_tx_id(&tx1);
1499 let id2 = crate::block::calculate_tx_id(&tx2);
1500
1501 assert_ne!(id1, id2);
1502 }
1503
1504 #[test]
1505 fn test_calculate_transaction_size() {
1506 let tx = create_valid_transaction();
1507 let size = calculate_transaction_size(&tx);
1508
1509 assert!(size > 0);
1510
1511 let size2 = calculate_transaction_size(&tx);
1513 assert_eq!(size, size2);
1514 }
1515
1516 #[test]
1517 fn test_calculate_transaction_size_multiple_inputs_outputs() {
1518 let mut tx = create_valid_transaction();
1519 tx.inputs.push(create_dummy_input());
1520 tx.outputs.push(create_dummy_output());
1521
1522 let size = calculate_transaction_size(&tx);
1523 assert!(size > 0);
1524 }
1525
1526 #[test]
1527 fn test_is_coinbase_true() {
1528 let coinbase_tx = create_coinbase_transaction();
1529 assert!(is_coinbase(&coinbase_tx));
1530 }
1531
1532 #[test]
1533 fn test_is_coinbase_false() {
1534 let regular_tx = create_valid_transaction();
1535 assert!(!is_coinbase(®ular_tx));
1536 }
1537
1538 fn create_valid_transaction() -> Transaction {
1540 Transaction {
1541 version: 1,
1542 inputs: vec![create_dummy_input()].into(),
1543 outputs: vec![create_dummy_output()].into(),
1544 lock_time: 0,
1545 }
1546 }
1547
1548 fn create_dummy_input() -> TransactionInput {
1549 TransactionInput {
1550 prevout: OutPoint {
1551 hash: [1; 32],
1552 index: 0,
1553 },
1554 script_sig: vec![OP_1],
1555 sequence: 0xffffffff,
1556 }
1557 }
1558
1559 fn create_dummy_output() -> TransactionOutput {
1560 TransactionOutput {
1561 value: 1000,
1562 script_pubkey: vec![OP_1].into(), }
1564 }
1565
1566 fn create_test_utxo_set() -> UtxoSet {
1567 let mut utxo_set = UtxoSet::default();
1568 let outpoint = OutPoint {
1569 hash: [1; 32],
1570 index: 0,
1571 };
1572 let utxo = UTXO {
1573 value: 10000,
1574 script_pubkey: vec![OP_1].into(), height: 0,
1576 is_coinbase: false,
1577 };
1578 utxo_set.insert(outpoint, std::sync::Arc::new(utxo));
1579 utxo_set
1580 }
1581
1582 fn create_coinbase_transaction() -> Transaction {
1583 Transaction {
1584 version: 1,
1585 inputs: vec![TransactionInput {
1586 prevout: OutPoint {
1587 hash: [0; 32].into(),
1588 index: 0xffffffff,
1589 },
1590 script_sig: vec![],
1591 sequence: 0xffffffff,
1592 }]
1593 .into(),
1594 outputs: vec![TransactionOutput {
1595 value: 5000000000,
1596 script_pubkey: vec![].into(),
1597 }]
1598 .into(),
1599 lock_time: 0,
1600 }
1601 }
1602}